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. +![](../images/rhel9_disk-layout-1.png) + +Once created, change the volume group for `root` and `swap` logical volume to `vg0` +![](../images/rhel9_disk-layout-2.png) + +When finished, it will look like this. +![](../images/rhel9_disk-layout-3.png) + + +Wait until installation is finished and press reboot. + +### Configure the system to match Hetzner requirements +Once installed and rebooted, login with previously given credentials and adjust to met Hetzner requirements accordingly. + +#### Install package dependencies and upgrade the OS to latest version. +``` +# dnf install -y lvm2 mdadm tar bzip2 +# dnf upgrade +``` + +#### Disable LVM system.devices +In RHEL 9, `system.devices` became [default](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9/pdf/configuring_and_managing_logical_volumes/red_hat_enterprise_linux-9-configuring_and_managing_logical_volumes-en-us.pdf), which is not recommended for the image-based installation for Hetzner. For that, let's disable this. +``` +# rm -f /etc/lvm/devices/system.devices +# sed -i -E 's/\s+# use_devicesfile = 0/ use_devicesfile = 0/' /etc/lvm/lvm.conf +``` + +#### enable autoassembly of special devices +To allow RAID and LVM devices scanned during boot, `rd.auto` needs to be enabled. +``` +# grubby --update-kernel=/boot/vmlinuz-5.14.0-162.6.1.el9_1.x86_64 --args=rd.auto +``` + +#### Create a symlink for dracut +Hetzner creates a ram disk and uses the dracut tool. It expects dracut to be installed under /sbin. This is not the case since RHEL 8 so we will add a symlink. +``` +# ln -s /usr/bin/dracut /sbin/dracut +``` + +#### Cleanup and finish image creation +Remove not required wireless firmware-drivers +``` +# dnf remove iwl* +``` + +Unregister and remove cached files +``` +# subscription-manager unregister +# subscription-manager clean +# dnf clean all +# rm -rf /etc/ssh/ssh_host_* +``` + +Finally clean the history +``` +# history -c +``` + +Now it's time to create the image-archive, which can be uploaded to the rescue-shell +``` +# tar cJvf /CentOS-91-el-x86_64-minimal.tar.xz --exclude=/dev --exclude=/proc --exclude=/sys --exclude=/CentOS-91-el-x86_64-minimal.tar.xz / +``` + + +## Install the image to your server +Boot the Hetzner System into Rescue Shell, create `config.txt` and upload the image to your `/root`-folder for use with `installimage` tool. + +Based on the image creation, which name for volume group was chosen, one need to adjust the `PART lvm vg0` in the `config.txt` file. Below is the example, based on the image created above. + +``` +DRIVE1 /dev/sda +DRIVE2 /dev/sdb +DRIVE3 /dev/sdc +DRIVE4 /dev/sdd + +SWRAID 1 +SWRAIDLEVEL 0 +BOOTLOADER grub +HOSTNAME lab.froemer.net +PART /boot ext4 1024M +PART lvm vg0 500G +PART lvm vg1 all + +LV vg0 root / xfs 40G +LV vg0 swap swap swap 15G +LV vg0 home /home xfs 30G +LV vg0 tmp /tmp xfs 5G +LV vg0 var /var xfs 10G +LV vg0 libvirt /var/lib/libvirt/images xfs all + +LV vg1 storage /data xfs all + +IMAGE /root/CentOS-91-el-x86_64-minimal.tar.xz +``` + + +To install the image, run `installimage` command. +``` +# installimage -a -c config.txt +``` + +The output should be expected to look like the following. +```shell + + Hetzner Online GmbH - installimage + + Your server will be installed now, this will take some minutes + You can abort at any time with CTRL+C ... + + : Reading configuration done + : Loading image file variables done + : Loading centos specific functions done + 1/17 : Deleting partitions done + 2/17 : Test partition size done + 3/17 : Creating partitions and /etc/fstab done + 4/17 : Creating software RAID level 0 done + 5/17 : Creating LVM volumes done + 6/17 : Formatting partitions + : formatting /dev/md/0 with ext4 done + : formatting /dev/vg0/root with xfs done + : formatting /dev/vg0/swap with swap done + : formatting /dev/vg0/home with xfs done + : formatting /dev/vg0/tmp with xfs done + : formatting /dev/vg0/var with xfs done + : formatting /dev/vg0/libvirt with xfs done + : formatting /dev/vg1/storage with xfs done + 7/17 : Mounting partitions done + 8/17 : Sync time via ntp done + : Importing public key for image validation done + 9/17 : Validating image before starting extraction warn + : No detached signature file found! + 10/17 : Extracting image (local) done + 11/17 : Setting up network config done + 12/17 : Executing additional commands + : Setting hostname done + : Generating new SSH keys done + : Generating mdadm config done + : Generating ramdisk done + : Generating ntp config done + 13/17 : Setting up miscellaneous files done + 14/17 : Configuring authentication + : Fetching SSH keys done + : Disabling root password done + : Disabling SSH root login with password done + : Copying SSH keys done + 15/17 : Installing bootloader grub done + 16/17 : Running some centos specific functions done + 17/17 : Clearing log files done + + INSTALLATION COMPLETE + You can now reboot and log in to your new system with the + same credentials that you used to log into the rescue system. +``` + +That's it - perform a `reboot` and have fun. \ No newline at end of file diff --git a/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 ```