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..8bbf02c0
--- /dev/null
+++ b/.tekton/pre-commit.yaml
@@ -0,0 +1,134 @@
+---
+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/tasks/github-add-comment.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 }}"
+ - 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
+ 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:
+ results:
+ - name: linter-output
+ description: Output of pre-commit run
+ workspaces:
+ - name: source
+ steps:
+ - name: pre-commit
+ image: quay.io/redhat-emea-ssa-team/hetzner-ocp4-pre-commit:latest
+ workingDir: $(workspaces.source.path)
+ script: |
+ 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
+ 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 }}"
diff --git a/.tekton/tasks/github-add-comment.yaml b/.tekton/tasks/github-add-comment.yaml
new file mode 100644
index 00000000..8330f43a
--- /dev/null
+++ b/.tekton/tasks/github-add-comment.yaml
@@ -0,0 +1,200 @@
+---
+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: PAC_GITHUB_SECRET
+ description: |
+ The name of the Kubernetes Secret that contains the GitHub token.
+ type: string
+
+ - name: PAC_GITHUB_SECRET_KEY
+ description: |
+ The key within the Kubernetes Secret that contains the GitHub token.
+ type: string
+ default: .git-credentials
+
+ - 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: GIT_CREDENTIALS
+ valueFrom:
+ secretKeyRef:
+ name: $(params.PAC_GITHUB_SECRET)
+ key: $(params.PAC_GITHUB_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
+
+ bearer = urllib.parse.urlparse(os.environ["GIT_CREDENTIALS"])
+
+ authHeader = "$(params.AUTH_TYPE) " + bearer.password
+
+ 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])
+
+ # 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)")
+
+ 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"))
diff --git a/README.md b/README.md
index 31daa227..152728ab 100644
--- a/README.md
+++ b/README.md
@@ -28,6 +28,8 @@ 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
+- RHEL 9 ([How to install RHEL9](docs/hetzner_rhel9.md))
## Infra providers
* [Hetzner CentOS](docs/hetzner.md)
@@ -69,10 +71,10 @@ 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.3-for-rhel-8-x86_64-rpms
-yum install -y ansible-navigator git podman
+dnf install -y ansible-navigator git podman
```
@@ -85,7 +87,6 @@ dnf install -y python3-pip podman git
python3 -m pip install ansible-navigator --user
echo 'export PATH=$HOME/.local/bin:$PATH' >> ~/.profile
source ~/.profile
-
```
## Initialize tools
@@ -111,7 +112,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** |
@@ -166,7 +167,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 +184,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`
|
@@ -207,6 +209,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
@@ -227,6 +230,22 @@ 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)
+
+
+# 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
diff --git a/ansible-navigator.yaml b/ansible-navigator.yaml
index 221ea557..aec9f8ab 100644
--- a/ansible-navigator.yaml
+++ b/ansible-navigator.yaml
@@ -2,11 +2,11 @@
ansible-navigator:
execution-environment:
container-options:
- - --net=host
+ - --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
- 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.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
diff --git a/ansible/00-provision-hetzner.yml b/ansible/00-provision-hetzner.yml
index c8ca4a72..fb41c2a6 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
@@ -18,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
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 50a29b0c..68a2e83a 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!"
@@ -65,8 +65,18 @@
- 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
- debug:
+ ansible.builtin.debug:
msg: "{{ item }}={{ lookup('vars',item) }}"
verbosity: 2
with_items:
@@ -80,7 +90,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 +104,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 +119,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,10 +136,19 @@
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:
- 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/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..8645afe6 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:
@@ -143,25 +143,40 @@
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
- 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 +192,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 +207,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 +223,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 +242,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 +258,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:
@@ -260,13 +275,25 @@
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
- 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/defaults/main.yml b/ansible/roles/openshift-4-cluster/defaults/main.yml
index 860e7c92..f26ca2ab 100644
--- a/ansible/roles/openshift-4-cluster/defaults/main.yml
+++ b/ansible/roles/openshift-4-cluster/defaults/main.yml
@@ -1,12 +1,12 @@
---
-
-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"
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:
@@ -42,7 +42,7 @@ 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: "/opt/openshift-install-{{ openshift_version }}/openshift-install"
# dev-pre:
# {{ openshift_mirror }}/pub/openshift-v4/clients/ocp-dev-preview
@@ -59,7 +59,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
@@ -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
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..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,25 +1,28 @@
---
-- name: Create own kubeconfig directory
- 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
- 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
- set_fact:
+ delegate_to: localhost
+ ansible.builtin.set_fact:
kubeconfig: "{{ kubeconfig_raw['content'] | b64decode | from_yaml }}"
# - name: Fetch Kubeconfig
@@ -27,32 +30,37 @@
# kubeconfig: "{{ lookup('file', openshift_install_dir ~ '/config/kubeconfig' ) | from_yaml }}"
- name: Select cluster & user
- set_fact:
+ 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
- set_fact:
- k8s_kubeconfig: "{{ openshift_install_dir }}/config/kubeconfig"
+ delegate_to: localhost
+ ansible.builtin.set_fact:
+ 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
- copy:
+ delegate_to: localhost
+ ansible.builtin.copy:
content: "{{ cluster['certificate-authority-data'] | b64decode }}"
dest: "{{ k8s_ca_cert }}"
mode: 0644
- name: Create k8s_client_key
- copy:
+ delegate_to: localhost
+ ansible.builtin.copy:
content: "{{ user['client-key-data'] | b64decode }}"
dest: "{{ k8s_client_key }}"
mode: 0644
- name: Create k8s_client_cert
- copy:
+ delegate_to: localhost
+ 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..0545d77a 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 }}"
@@ -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-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..c09c5829 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:
@@ -151,12 +151,14 @@
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'
- 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..8e58d205 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,8 @@
- add-ons
- post-install-add-ons
-- 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 }}"
@@ -26,12 +27,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 }}"
@@ -69,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
@@ -76,17 +79,23 @@
# 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
+ - name: Include tasks
+ 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
- include_tasks: create-ignition.yml
+ 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
- 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 +107,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 +121,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 +136,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..b3b6c42c 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:
@@ -15,19 +15,21 @@
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'
- 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 +38,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..e28b35dc 100644
--- a/ansible/roles/openshift-4-cluster/tasks/destroy.yml
+++ b/ansible/roles/openshift-4-cluster/tasks/destroy.yml
@@ -1,36 +1,42 @@
---
-- import_tasks: destroy-network.yml
+- name: Destroy network
+ 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
+- name: Destroy bootstrap VM
+ ansible.builtin.include_tasks: destroy-vm.yml
vars:
vm_instance_name: "{{ cluster_name }}-bootstrap"
-- 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
-- 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
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
+ tags:
+ - storage
+ - skip_ansible_lint
- 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..05561668 100644
--- a/ansible/roles/openshift-4-cluster/tasks/download-openshift-artifacts.yml
+++ b/ansible/roles/openshift-4-cluster/tasks/download-openshift-artifacts.yml
@@ -1,27 +1,28 @@
---
- 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 }}"
- "{{ tmp_openshift_client_download_url }}"
+ - "{{ openshift_location }}/oc-mirror.tar.gz"
- "{{ opm_download_url }}"
- "{{ helm_cli_location }}"
- "{{ 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 +33,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,32 +54,32 @@
# 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 }}"
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
- 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 +87,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 +104,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 +114,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 +126,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
@@ -136,8 +137,20 @@
- '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)
- unarchive:
+ ansible.builtin.unarchive:
src: "{{ opm_download_url }}"
dest: "{{ opm_dest }}"
remote_src: yes
@@ -150,33 +163,34 @@
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
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"
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..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
@@ -6,32 +6,32 @@
#
- 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'
+ 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
+ enabled: true
- name: Add nfs exports to /etc/exports
- blockinfile:
+ ansible.builtin.blockinfile:
path: /etc/exports
- backup: yes
- create: yes
+ backup: true
+ create: true
marker: "# {mark} OpenShift cluster - {{ cluster_name }}"
owner: root
group: root
@@ -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..c240891d 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,11 +71,13 @@
###########################################################################################
# Install letsencrypt certificates
###########################################################################################
-- 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
@@ -84,7 +86,7 @@
tags:
- post-install
- idp
- set_fact:
+ ansible.builtin.set_fact:
identity_providers: "[]"
- name: Handle auth_htpasswd
@@ -108,7 +110,7 @@
namespace: openshift-config
type: Opaque
- name: Create htpasswd identity provider template
- set_fact:
+ ansible.builtin.set_fact:
htpasswd_idp:
htpasswd:
fileData:
@@ -117,7 +119,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 +147,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 +158,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 +168,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 +194,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 +206,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 +269,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 +281,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 +291,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..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
@@ -1,7 +1,6 @@
---
-
- name: Installing KVM Packages
- yum:
+ ansible.builtin.package:
name:
- "@virtualization-hypervisor"
- "@virtualization-client"
@@ -13,18 +12,28 @@
state: present
- name: Upgrade all packages
- yum:
+ 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
- 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 +45,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 +58,7 @@
notify: 'reload firewalld'
- name: Allow OpenShift traffic from public to Host
- firewalld:
+ ansible.posix.firewalld:
zone: public
state: enabled
permanent: yes
@@ -61,4 +70,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..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
@@ -1,6 +1,20 @@
---
+
+- 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.3-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
- yum:
+ ansible.builtin.package:
name:
- "@virtualization-hypervisor"
- "@virtualization-client"
@@ -9,12 +23,22 @@
state: present
- name: Upgrade all packages
- yum:
+ 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
- firewalld:
+ ansible.posix.firewalld:
zone: libvirt
state: enabled
permanent: yes
@@ -26,7 +50,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 +63,7 @@
notify: 'reload firewalld'
- name: Allow OpenShift traffic from public to Host
- firewalld:
+ ansible.posix.firewalld:
zone: public
state: enabled
permanent: yes
@@ -51,4 +75,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-9.yml b/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-9.yml
new file mode 100644
index 00000000..d06301eb
--- /dev/null
+++ b/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-9.yml
@@ -0,0 +1,83 @@
+---
+- 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:
+ - "@virtualization-hypervisor"
+ - "@virtualization-client"
+ - "@virtualization-platform"
+ - "@virtualization-tools"
+ state: present
+
+- name: Installing playbook dependencies
+ ansible.builtin.package:
+ name: python3-lxml
+ state: present
+
+- name: Upgrade all packages
+ ansible.builtin.yum:
+ name: '*'
+ state: latest
+ update_only: true
+ 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:
+ 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/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..64919d41
--- /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
+ rhsm_repository:
+ name: "{{ rhsm_repository }}"
+ state: enabled
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..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
@@ -1,6 +1,6 @@
---
- name: Installing KVM Packages
- yum:
+ ansible.builtin.package:
name:
- "@virtualization-hypervisor"
- "@virtualization-client"
@@ -12,18 +12,28 @@
state: present
- name: Upgrade all packages
- yum:
+ 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
- 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 +45,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 +58,7 @@
notify: 'reload firewalld'
- name: Allow OpenShift traffic from public to Host
- firewalld:
+ ansible.posix.firewalld:
zone: public
state: enabled
permanent: yes
@@ -60,4 +70,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-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/ansible/roles/openshift-4-cluster/tasks/prepare-host.yml b/ansible/roles/openshift-4-cluster/tasks/prepare-host.yml
index 9ed65690..c4cbc15c 100644
--- a/ansible/roles/openshift-4-cluster/tasks/prepare-host.yml
+++ b/ansible/roles/openshift-4-cluster/tasks/prepare-host.yml
@@ -1,25 +1,29 @@
---
+- name: Initial reboot warning (off)
+ ansible.builtin.set_fact:
+ hetzner_ocp4_prepare_host_reboot_needed: false
+
- 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,14 +31,40 @@
# 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\]'
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"
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..f4e87523 100644
--- a/ansible/roles/openshift-4-cluster/tasks/start.yml
+++ b/ansible/roles/openshift-4-cluster/tasks/start.yml
@@ -4,19 +4,21 @@
# vars:
# vm_instance_name: "{{ cluster_name }}-bootstrap"
-- 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
-- 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
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:
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/ansible/roles/provision-hetzner/defaults/main.yml b/ansible/roles/provision-hetzner/defaults/main.yml
index 90f1388a..00194953 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
-ansible_python_interpreter: /usr/bin/python3 # target host python interpreter
+hetzner_size_of_libvirt_images: all
+
+robot_base: https://robot-ws.your-server.de/
+needs_reprovision: false
diff --git a/ansible/roles/provision-hetzner/handlers/main.yaml b/ansible/roles/provision-hetzner/handlers/main.yaml
new file mode 100644
index 00000000..1f74c06e
--- /dev/null
+++ b/ansible/roles/provision-hetzner/handlers/main.yaml
@@ -0,0 +1,6 @@
+---
+- name: Restart SSH daemon
+ ansible.builtin.service:
+ 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 ad021f82..733b48d7 100644
--- a/ansible/roles/provision-hetzner/tasks/main.yml
+++ b/ansible/roles/provision-hetzner/tasks/main.yml
@@ -1,143 +1,28 @@
---
-- 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: "Retrieve current setup of {{ hetzner_ip }}"
+ ansible.builtin.gather_facts:
+ register: host_facts
+ delegate_to: "{{ hetzner_ip }}"
-- 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: Check Boot image and hetzner_force_provisioning
+ 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 )
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: provision-server.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 }}"
+ tags:
+ - skip_ansible_lint
- name: Set ansible_python_interpreter to /usr/libexec/platform-python (RHEL 8)
set_fact:
@@ -149,8 +34,6 @@
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
diff --git a/ansible/roles/provision-hetzner/tasks/provision-server.yml b/ansible/roles/provision-hetzner/tasks/provision-server.yml
new file mode 100644
index 00000000..ad505932
--- /dev/null
+++ b/ansible/roles/provision-hetzner/tasks/provision-server.yml
@@ -0,0 +1,200 @@
+---
+- 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
+# 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 }}"
+
+- 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: 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
+ ansible.builtin.fail:
+ msg: "Can NOT find Hetzner Server Id, to many or no matching server found."
+ when: server_id_list|length != 1
+
+- name: Filter server by IPv4 or IPv6
+ delegate_to: localhost
+ set_fact:
+ server_id: "{{ server_id_list | first }}"
+
+- 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') or
+ result.stdout is search('Cancelled')
+ - 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/templates/autosetup b/ansible/roles/provision-hetzner/templates/autosetup
index 7b4f1dea..d8b4f22c 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 libvirt /var/lib/libvirt/images xfs all
+LV vg0 home /home xfs 10G
+LV vg0 var /var xfs 50G
+LV vg0 libvirt /var/lib/libvirt/images xfs {{ hetzner_size_of_libvirt_images }}
IMAGE {{ hetzner_image }}
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/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
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/hetzner.md b/docs/hetzner.md
index 46e31a07..f1c69c6a 100644
--- a/docs/hetzner.md
+++ b/docs/hetzner.md
@@ -60,8 +60,8 @@ 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/)
+* [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.
+
+
+Once created, change the volume group for `root` and `swap` logical volume to `vg0`
+
+
+When finished, it will look like this.
+
+
+
+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/docs/release-notes.md b/docs/release-notes.md
index d5ee18f3..af184de0 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -1,5 +1,24 @@
# RELEASE NOTES
+## 2022-12-17
+
+ * 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`
+ * 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)
+ * 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
+
+>>>>>>> dc2f464 (Add Rocky Linux 9 support)
+
+
## 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/
+```
diff --git a/images/rhel9_disk-layout-1.png b/images/rhel9_disk-layout-1.png
new file mode 100644
index 00000000..0f5057bc
Binary files /dev/null and b/images/rhel9_disk-layout-1.png differ
diff --git a/images/rhel9_disk-layout-2.png b/images/rhel9_disk-layout-2.png
new file mode 100644
index 00000000..c1f72154
Binary files /dev/null and b/images/rhel9_disk-layout-2.png differ
diff --git a/images/rhel9_disk-layout-3.png b/images/rhel9_disk-layout-3.png
new file mode 100644
index 00000000..b92297f7
Binary files /dev/null and b/images/rhel9_disk-layout-3.png differ
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
```