From 7540964d6d5c601b20595bc65e52801647801a19 Mon Sep 17 00:00:00 2001 From: Chris Keller Date: Wed, 29 Nov 2023 20:52:32 -0500 Subject: [PATCH] Initial assisted installer work. --- assisted-provision.yaml | 65 +++++++++++ assisted-retire.yaml | 59 ++++++++++ requirements.txt | 1 + roles/assisted_installer/defaults/main.yaml | 3 + roles/assisted_installer/tasks/create.yaml | 61 ++++++++++ .../assisted_installer/tasks/credentials.yaml | 33 ++++++ roles/assisted_installer/tasks/delete.yaml | 8 ++ roles/assisted_installer/tasks/host-role.yaml | 11 ++ roles/assisted_installer/tasks/host-wait.yaml | 47 ++++++++ roles/assisted_installer/tasks/hosts.yaml | 30 +++++ .../tasks/install-wait.yaml | 39 +++++++ roles/assisted_installer/tasks/install.yaml | 35 ++++++ roles/assisted_installer/tasks/main.yaml | 64 +++++++++++ roles/assisted_installer/tasks/network.yaml | 9 ++ roles/assisted_installer/tasks/storage.yaml | 54 +++++++++ .../assisted_installer/templates/boot-disk.j2 | 14 +++ .../templates/credentials.j2 | 3 + .../templates/infra-envs.j2 | 45 ++++++++ roles/assisted_installer/templates/network.j2 | 32 ++++++ .../defaults/main.yaml | 1 + .../vmware_assisted_installer/tasks/boot.yaml | 104 ++++++++++++++++++ .../tasks/create.yaml | 96 ++++++++++++++++ .../vmware_assisted_installer/tasks/main.yaml | 11 ++ 23 files changed, 825 insertions(+) create mode 100644 assisted-provision.yaml create mode 100644 assisted-retire.yaml create mode 100644 roles/assisted_installer/defaults/main.yaml create mode 100644 roles/assisted_installer/tasks/create.yaml create mode 100644 roles/assisted_installer/tasks/credentials.yaml create mode 100644 roles/assisted_installer/tasks/delete.yaml create mode 100644 roles/assisted_installer/tasks/host-role.yaml create mode 100644 roles/assisted_installer/tasks/host-wait.yaml create mode 100644 roles/assisted_installer/tasks/hosts.yaml create mode 100644 roles/assisted_installer/tasks/install-wait.yaml create mode 100644 roles/assisted_installer/tasks/install.yaml create mode 100644 roles/assisted_installer/tasks/main.yaml create mode 100644 roles/assisted_installer/tasks/network.yaml create mode 100644 roles/assisted_installer/tasks/storage.yaml create mode 100644 roles/assisted_installer/templates/boot-disk.j2 create mode 100644 roles/assisted_installer/templates/credentials.j2 create mode 100644 roles/assisted_installer/templates/infra-envs.j2 create mode 100644 roles/assisted_installer/templates/network.j2 create mode 100644 roles/vmware_assisted_installer/defaults/main.yaml create mode 100644 roles/vmware_assisted_installer/tasks/boot.yaml create mode 100644 roles/vmware_assisted_installer/tasks/create.yaml create mode 100644 roles/vmware_assisted_installer/tasks/main.yaml diff --git a/assisted-provision.yaml b/assisted-provision.yaml new file mode 100644 index 0000000..a8d02e8 --- /dev/null +++ b/assisted-provision.yaml @@ -0,0 +1,65 @@ +- name: Initialize VMware and Deploy Cluster + gather_facts: false + hosts: localhost + vars_files: + vault.yaml + tasks: + - name: Create VMs + vars: + _vmware_assisted_installer_action: create + ansible.builtin.include_role: + name: vmware_assisted_installer + + - name: Create Cluster via Assisted API + vars: + _assisted_installer_action: create + ansible.builtin.include_role: + name: assisted_installer + + - name: Reconfigure and Boot VMs + vars: + _vmware_assisted_installer_action: boot + ansible.builtin.include_role: + name: vmware_assisted_installer + + - name: Wait for Hosts + vars: + _assisted_installer_action: host-wait + ansible.builtin.include_role: + name: assisted_installer + + - name: Assign Host Roles + vars: + _assisted_installer_action: host-role + ansible.builtin.include_role: + name: assisted_installer + + - name: Setup Storage + vars: + _assisted_installer_action: storage + ansible.builtin.include_role: + name: assisted_installer + + - name: Setup Networking + vars: + _assisted_installer_action: network + ansible.builtin.include_role: + name: assisted_installer + + - name: Start Installation + vars: + _assisted_installer_action: install + ansible.builtin.include_role: + name: assisted_installer + + - name: Wait for Install + vars: + _assisted_installer_action: install-wait + ansible.builtin.include_role: + name: assisted_installer + + - name: Save Credentials + vars: + _assisted_installer_action: credentials + ansible.builtin.include_role: + name: assisted_installer diff --git a/assisted-retire.yaml b/assisted-retire.yaml new file mode 100644 index 0000000..9644f97 --- /dev/null +++ b/assisted-retire.yaml @@ -0,0 +1,59 @@ +- name: Initialize VMware and Deploy Cluster Nodes + gather_facts: false + hosts: localhost + vars_files: + vault.yaml + tasks: + - name: Clean up ~/.ssh/known_hosts + when: + - cleanup_known_hosts is defined + - cleanup_known_hosts + block: + - name: Remove Lines w/ Inventory Hostname in known_hosts + ansible.builtin.lineinfile: + dest: ~/.ssh/known_hosts + regexp: "^.*{{ item }}.{{ base_domain }}.*$" + state: absent + loop: "{{ groups[provision_group] }}" + + - name: Remove Lines w/ Inventory IP in known_hosts + ansible.builtin.lineinfile: + dest: ~/.ssh/known_hosts + regexp: "^.*{{ lookup('dig', item) }}.*$" + state: absent + loop: "{{ groups[provision_group] }}" + + - name: Lookup infra-env-id + ansible.builtin.set_fact: + infra_env_id: "{{ lookup('ansible.builtin.file', artifact_directory + '/infra-env-id') }}" + + - name: Delete ISO From Download Path + ansible.builtin.file: + path: "{{ discovery_iso_download_path }}/{{ cluster_name }}-{{ infra_env_id }}-{{ discovery_iso_type }}.iso" + state: absent + + - name: Delete Cluster from Red Hat Cloud Console + vars: + _assisted_installer_action: delete + ansible.builtin.import_role: + name: assisted_installer + tags: + - assisted-api + + - name: Retire VMs + ansible.builtin.import_role: + name: vmware_retire + tags: + - vmware + + - name: Delete ISO From Datastore + community.vmware.vsphere_file: + datacenter: "{{ vcenter_datacenter }}" + datastore: "{{ discovery_iso_datastore }}" + hostname: "{{ vcenter_hostname }}" + password: "{{ vcenter_password }}" + path: "{{ cluster_name }}-{{ infra_env_id }}-{{ discovery_iso_type }}.iso" + username: "{{ vcenter_username }}" + validate_certs: "{{ validate_certs }}" + tags: + - vmware diff --git a/requirements.txt b/requirements.txt index fbc3d7a..dc55b7a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ ansible +ansible-lint dnspython jmespath openshift diff --git a/roles/assisted_installer/defaults/main.yaml b/roles/assisted_installer/defaults/main.yaml new file mode 100644 index 0000000..d6be8a3 --- /dev/null +++ b/roles/assisted_installer/defaults/main.yaml @@ -0,0 +1,3 @@ +redhat_sso_url: https://sso.redhat.com/auth/realms/redhat-external/protocol/openid-connect/token +api_clusters_url: https://api.openshift.com/api/assisted-install/v2/clusters/ +api_infra_envs_url: https://api.openshift.com/api/assisted-install/v2/infra-envs/ diff --git a/roles/assisted_installer/tasks/create.yaml b/roles/assisted_installer/tasks/create.yaml new file mode 100644 index 0000000..7343642 --- /dev/null +++ b/roles/assisted_installer/tasks/create.yaml @@ -0,0 +1,61 @@ +- name: Query Clusters + ansible.builtin.uri: + headers: + Authorization: "Bearer {{ access_token.json.access_token }}" + method: GET + status_code: 200 + url: "{{ api_clusters_url }}" + register: api_query_results + +- name: Create "cluster" Resource + ansible.builtin.uri: + body: + name: "{{ cluster_name }}" + high_availability_mode: "{{ high_availability_mode }}" + openshift_version: "{{ openshift_y_release }}" + pull_secret: '{{ pull_secret | from_json | to_json }}' + base_dns_domain: "{{ base_dns_domain }}" + cpu_architecture: "{{ cpu_architecture }}" + disk_encryption: "{{ disk_encryption }}" + body_format: json + headers: + Authorization: "Bearer {{ access_token.json.access_token }}" + method: POST + status_code: 201 + url: "{{ api_clusters_url }}" + register: api_create_cluster_results + +- name: Set cluster_id Fact + ansible.builtin.set_fact: + cluster_id: "{{ api_create_cluster_results.json.id }}" + +- name: Debug cluster_id Fact + ansible.builtin.debug: + var: cluster_id + +- name: Write cluster_id to Artifact Directory + ansible.builtin.copy: + content: "{{ cluster_id }}" + dest: "{{ artifact_directory }}/cluster-id" + mode: "0644" + +- name: Create infra-env Resource + ansible.builtin.uri: + body: "{{ lookup('ansible.builtin.template', 'templates/infra-envs.j2') }}" + body_format: json + headers: + Authorization: "Bearer {{ access_token.json.access_token }}" + method: POST + status_code: 201 + url: "{{ api_infra_envs_url }}" + register: api_create_infra_envs_results + +- name: Debug infra_env_id + ansible.builtin.debug: + msg: "{{ api_create_infra_envs_results.json.id }}" + +- name: Write infra_env_id to Artifact Directory + ansible.builtin.copy: + content: "{{ api_create_infra_envs_results.json.id }}" + dest: "{{ artifact_directory }}/infra-env-id" + mode: "0644" diff --git a/roles/assisted_installer/tasks/credentials.yaml b/roles/assisted_installer/tasks/credentials.yaml new file mode 100644 index 0000000..0c5419f --- /dev/null +++ b/roles/assisted_installer/tasks/credentials.yaml @@ -0,0 +1,33 @@ +- name: Query Cluster Credentials + ansible.builtin.uri: + headers: + Authorization: "Bearer {{ access_token.json.access_token }}" + method: GET + status_code: 200 + url: "{{ api_clusters_url + cluster_id }}/credentials" + register: api_query_credentials_results + +- name: Write Credentials to Artifact Directory + vars: + _console_password: "{{ api_query_credentials_results.json.password }}" + _console_url: "{{ api_query_credentials_results.json.console_url }}" + _console_username: "{{ api_query_credentials_results.json.username }}" + ansible.builtin.copy: + content: "{{ lookup('ansible.builtin.template', 'templates/credentials.j2') }}" + dest: "{{ artifact_directory }}/credentials" + mode: "0600" + +- name: Query kubeconfig URL + ansible.builtin.uri: + headers: + Authorization: "Bearer {{ access_token.json.access_token }}" + method: GET + status_code: 200 + url: "{{ api_clusters_url + cluster_id }}/downloads/credentials-presigned?file_name=kubeconfig" + register: api_query_kubeconfig_url_results + +- name: Download kubeconfig + ansible.builtin.get_url: + dest: "{{ artifact_directory }}/kubeconfig" + url: "{{ api_query_kubeconfig_url_results.json.url }}" + mode: "0600" diff --git a/roles/assisted_installer/tasks/delete.yaml b/roles/assisted_installer/tasks/delete.yaml new file mode 100644 index 0000000..1b8c77f --- /dev/null +++ b/roles/assisted_installer/tasks/delete.yaml @@ -0,0 +1,8 @@ +- name: Delete "cluster" Resource + ansible.builtin.uri: + headers: + Authorization: "Bearer {{ access_token.json.access_token }}" + method: DELETE + status_code: 204 + url: "{{ api_clusters_url }}/{{ lookup('ansible.builtin.file', artifact_directory + '/cluster-id') }}" + register: api_create_cluster_results diff --git a/roles/assisted_installer/tasks/host-role.yaml b/roles/assisted_installer/tasks/host-role.yaml new file mode 100644 index 0000000..ae751a6 --- /dev/null +++ b/roles/assisted_installer/tasks/host-role.yaml @@ -0,0 +1,11 @@ +- name: Set Host Role + ansible.builtin.uri: + body: + host_role: "{{ hostvars[item.host].api_node_type }}" + body_format: json + headers: + Authorization: "Bearer {{ access_token.json.access_token }}" + method: PATCH + status_code: 201 + url: "{{ api_infra_envs_url + api_create_infra_envs_results.json.id }}/hosts/{{ item.id }}" + loop: "{{ _assisted_host_info }}" diff --git a/roles/assisted_installer/tasks/host-wait.yaml b/roles/assisted_installer/tasks/host-wait.yaml new file mode 100644 index 0000000..51e66e5 --- /dev/null +++ b/roles/assisted_installer/tasks/host-wait.yaml @@ -0,0 +1,47 @@ +- name: Query Clusters + ansible.builtin.uri: + headers: + Authorization: "Bearer {{ access_token.json.access_token }}" + method: GET + status_code: 200 + url: "{{ api_clusters_url }}" + register: api_query_results + +- name: Set Expected Host Count + ansible.builtin.set_fact: + _inventory_host_count: "{{ groups[provision_group] | count }}" + +- name: Query Hosts + ansible.builtin.uri: + headers: + Authorization: "Bearer {{ access_token.json.access_token }}" + method: GET + status_code: 200 + url: "{{ api_infra_envs_url + api_create_infra_envs_results.json.id }}/hosts" + delay: 10 + register: api_query_hosts_results + retries: 180 + until: + - api_query_hosts_results.json is defined + - (api_query_hosts_results.json | count) == (_inventory_host_count | int) + - (api_query_hosts_results.json | json_query(_query_simple_status) | count) == (_inventory_host_count | int) + vars: + _query_simple_status: "[?status=='pending-for-input' || status=='known' || status=='installed']" + +- name: Combine Applicable Host Properties into Dictionary for Easy Lookup + ansible.builtin.set_fact: + _assisted_host_info: >- + {{ + _assisted_host_info | default([]) + + [ + { + 'host': item.requested_hostname | split('.') | first, + 'id': item.id, + 'inventory': item.inventory | from_json, + 'status': item.status, + 'status_info': item.status_info, + 'validations_info': item.validations_info | from_json + } + ] + }} + loop: "{{ api_query_hosts_results.json }}" diff --git a/roles/assisted_installer/tasks/hosts.yaml b/roles/assisted_installer/tasks/hosts.yaml new file mode 100644 index 0000000..f3771d8 --- /dev/null +++ b/roles/assisted_installer/tasks/hosts.yaml @@ -0,0 +1,30 @@ +- name: Query Hosts + ansible.builtin.uri: + headers: + Authorization: "Bearer {{ access_token.json.access_token }}" + method: GET + status_code: 200 + url: "{{ api_infra_envs_url + api_create_infra_envs_results.json.id }}/hosts" + register: api_query_hosts_results + +- name: Reset Variable + ansible.builtin.set_fact: + _assisted_host_info: [] + +- name: Combine Applicable Host Properties into Dictionary for Easy Lookup + ansible.builtin.set_fact: + _assisted_host_info: >- + {{ + _assisted_host_info + + [ + { + 'host': item.requested_hostname | split('.') | first, + 'id': item.id, + 'inventory': item.inventory | from_json, + 'status': item.status, + 'status_info': item.status_info, + 'validations_info': item.validations_info | from_json + } + ] + }} + loop: "{{ api_query_hosts_results.json }}" diff --git a/roles/assisted_installer/tasks/install-wait.yaml b/roles/assisted_installer/tasks/install-wait.yaml new file mode 100644 index 0000000..4f9a785 --- /dev/null +++ b/roles/assisted_installer/tasks/install-wait.yaml @@ -0,0 +1,39 @@ +- name: Wait for Cluster to Finish Installation + block: + - name: Generate Access Token + ansible.builtin.uri: + body: + client_id: rhsm-api + grant_type: refresh_token + refresh_token: "{{ offline_token }}" + body_format: form-urlencoded + method: POST + url: "{{ redhat_sso_url }}" + register: access_token + + - name: Query Cluster Status + ansible.builtin.uri: + headers: + Authorization: "Bearer {{ access_token.json.access_token }}" + method: GET + status_code: 200 + url: "{{ api_clusters_url + cluster_id }}" + delay: 10 + register: api_query_cluster_results + retries: 180 + until: + - api_query_cluster_results.json is defined + - api_query_cluster_results.json.status is defined + - api_query_cluster_results.json.status == "installed" + rescue: + - name: Debug access_token + ansible.builtin.debug: + var: access_token + + - name: Debug api_query_cluster_results + ansible.builtin.debug: + var: api_query_cluster_results + + - name: Retry Infinitely + ansible.builtin.include_tasks: + file: install-wait.yaml diff --git a/roles/assisted_installer/tasks/install.yaml b/roles/assisted_installer/tasks/install.yaml new file mode 100644 index 0000000..f09f94c --- /dev/null +++ b/roles/assisted_installer/tasks/install.yaml @@ -0,0 +1,35 @@ +- name: Wait For Install Ready + block: + - name: Generate Access Token + ansible.builtin.uri: + body: + client_id: rhsm-api + grant_type: refresh_token + refresh_token: "{{ offline_token }}" + body_format: form-urlencoded + method: POST + url: "{{ redhat_sso_url }}" + register: access_token + + - name: Query Cluster Status + ansible.builtin.uri: + headers: + Authorization: "Bearer {{ access_token.json.access_token }}" + method: GET + status_code: 200 + url: "{{ api_clusters_url + cluster_id }}" + delay: 30 + register: api_query_cluster_results + retries: 60 + until: + - api_query_cluster_results.json is defined + - api_query_cluster_results.json.status is defined + - api_query_cluster_results.json.status == "ready" + +- name: Initiate Cluster Install + ansible.builtin.uri: + headers: + Authorization: "Bearer {{ access_token.json.access_token }}" + method: POST + status_code: 202 + url: "{{ api_clusters_url + cluster_id }}/actions/install" diff --git a/roles/assisted_installer/tasks/main.yaml b/roles/assisted_installer/tasks/main.yaml new file mode 100644 index 0000000..67d749b --- /dev/null +++ b/roles/assisted_installer/tasks/main.yaml @@ -0,0 +1,64 @@ +- name: Generate Access Token + ansible.builtin.uri: + body: + client_id: rhsm-api + grant_type: refresh_token + refresh_token: "{{ offline_token }}" + body_format: form-urlencoded + method: POST + url: "{{ redhat_sso_url }}" + register: access_token + +- name: Import Create Tasks + ansible.builtin.include_tasks: + file: create.yaml + when: + - _assisted_installer_action == "create" + +- name: Import Delete Tasks + ansible.builtin.include_tasks: + file: delete.yaml + when: + - _assisted_installer_action == "delete" + +- name: Import Host Wait Tasks + ansible.builtin.include_tasks: + file: host-wait.yaml + when: + - _assisted_installer_action == "host-wait" + +- name: Import Host Role Tasks + ansible.builtin.include_tasks: + file: host-role.yaml + when: + - _assisted_installer_action == "host-role" + +- name: Import Storage Tasks + ansible.builtin.include_tasks: + file: storage.yaml + when: + - _assisted_installer_action == "storage" + +- name: Import Network Tasks + ansible.builtin.include_tasks: + file: network.yaml + when: + - _assisted_installer_action == "network" + +- name: Import Install Tasks + ansible.builtin.include_tasks: + file: install.yaml + when: + - _assisted_installer_action == "install" + +- name: Import Install Wait Tasks + ansible.builtin.include_tasks: + file: install-wait.yaml + when: + - _assisted_installer_action == "install-wait" + +- name: Save Credentials + ansible.builtin.include_tasks: + file: credentials.yaml + when: + - _assisted_installer_action == "credentials" diff --git a/roles/assisted_installer/tasks/network.yaml b/roles/assisted_installer/tasks/network.yaml new file mode 100644 index 0000000..c7884ba --- /dev/null +++ b/roles/assisted_installer/tasks/network.yaml @@ -0,0 +1,9 @@ +- name: Update Cluster Networking + ansible.builtin.uri: + body: "{{ lookup('ansible.builtin.template', 'templates/network.j2') }}" + body_format: json + headers: + Authorization: "Bearer {{ access_token.json.access_token }}" + method: PATCH + status_code: 201 + url: "{{ api_clusters_url + cluster_id }}" diff --git a/roles/assisted_installer/tasks/storage.yaml b/roles/assisted_installer/tasks/storage.yaml new file mode 100644 index 0000000..6ee58a9 --- /dev/null +++ b/roles/assisted_installer/tasks/storage.yaml @@ -0,0 +1,54 @@ +- name: Lookup Installation Disks From Inventory + vars: + _query: "[?boot==`true`].size_gb" + ansible.builtin.set_fact: + _boot_disk_size: >- + {{ + _boot_disk_size | default([]) + + [ + { + 'host': item, + 'size_in_bytes': (hostvars[item].disks | json_query(_query) | first) * (1024 | ansible.builtin.pow(3)) | int + } + ] + }} + loop: "{{ groups[provision_group] }}" + +- name: Debug _boot_disk_size + ansible.builtin.debug: + var: _boot_disk_size + +- name: Build Host/Disk By ID Dictionary + vars: + _query_eligible: "[?installation_eligibility.eligible==`true`]" + _query_host_id: "[?host=='{{ item.host }}'].id" + _query_inventory: "[?host=='{{ item.host }}'].inventory.disks[]" + _query_size: "[?size_bytes==`{{ item.size_in_bytes }}`].by_id" + ansible.builtin.set_fact: + _boot_disks: >- + {{ + _boot_disks | default([]) + + [ + { + 'host': item.host, + 'id': _assisted_host_info | json_query(_query_host_id) | first, + 'disk_by_id': _assisted_host_info | json_query(_query_inventory) | json_query(_query_eligible) | json_query(_query_size) | first + } + ] + }} + loop: "{{ _boot_disk_size }}" + +- name: Debug _boot_disks + ansible.builtin.debug: + var: _boot_disks + +- name: Set Boot Disks + ansible.builtin.uri: + body: "{{ lookup('ansible.builtin.template', 'templates/boot-disk.j2') }}" + body_format: json + headers: + Authorization: "Bearer {{ access_token.json.access_token }}" + method: PATCH + status_code: 201 + url: "{{ api_infra_envs_url + api_create_infra_envs_results.json.id }}/hosts/{{ item.id }}" + loop: "{{ _boot_disks }}" diff --git a/roles/assisted_installer/templates/boot-disk.j2 b/roles/assisted_installer/templates/boot-disk.j2 new file mode 100644 index 0000000..892d3b1 --- /dev/null +++ b/roles/assisted_installer/templates/boot-disk.j2 @@ -0,0 +1,14 @@ +{ + "disks_selected_config": [ + { + "id": "{{ item.disk_by_id }}", + "role": "install" + } + ], + "disks_skip_formatting": [ + { + "disk_id": "{{ item.disk_by_id }}", + "skip_formatting": false + } + ] +} \ No newline at end of file diff --git a/roles/assisted_installer/templates/credentials.j2 b/roles/assisted_installer/templates/credentials.j2 new file mode 100644 index 0000000..2f41a20 --- /dev/null +++ b/roles/assisted_installer/templates/credentials.j2 @@ -0,0 +1,3 @@ +Console URL: {{ _console_url }} +Username: {{ _console_username }} +Password: {{ _console_password }} diff --git a/roles/assisted_installer/templates/infra-envs.j2 b/roles/assisted_installer/templates/infra-envs.j2 new file mode 100644 index 0000000..63f9c38 --- /dev/null +++ b/roles/assisted_installer/templates/infra-envs.j2 @@ -0,0 +1,45 @@ +{% macro static_network_config(ip, dns, gateway) -%} +interfaces: + - name: eth0 + type: ethernet + state: up + ipv4: + address: + - ip: {{ ip }} + prefix-length: 24 + enabled: true + dhcp: false +dns-resolver: + config: + server: + - {{ dns }} +routes: + config: + - destination: 0.0.0.0/0 + next-hop-address: {{ gateway }} + next-hop-interface: eth0 + table-id: 254 +{%- endmacro %} +{ + "additional_trust_bundle": "{{ additional_trust_bundle | default('') | regex_replace('\\n', '\\\\n') }}", + "cpu_architecture": "x86_64", + "cluster_id": "{{ cluster_id }}", + "image_type": "{{ discovery_iso_type }}", + "name": "prime_infra-env", + "openshift_version": "{{ openshift_y_release }}", + "pull_secret": "{{ pull_secret | from_json | to_json | regex_replace('\"', '\\\"') }}", + "ssh_authorized_key": "{{ ssh_authorized_key }}", + "static_network_config": [ +{% for i in mac_ip_dict %} + { + "network_yaml": "{{ static_network_config(i.ip, i.dns, i.gateway) | regex_replace('\\n', '\\\\n') }}", + "mac_interface_map": [ + { + "mac_address": "{{ i.mac }}", + "logical_nic_name": "eth0" + } + ] + }{{ "," if not loop.last else "" }} +{% endfor %} + ] +} diff --git a/roles/assisted_installer/templates/network.j2 b/roles/assisted_installer/templates/network.j2 new file mode 100644 index 0000000..7edf984 --- /dev/null +++ b/roles/assisted_installer/templates/network.j2 @@ -0,0 +1,32 @@ +{ + "api_vips": [ + { + "ip": "{{ vips.api }}", + "cluster_id": "{{ cluster_id }}" + } + ], + "api_vip": "{{ vips.api }}", + "ingress_vips": [ + { + "ip": "{{ vips.ingress }}", + "cluster_id": "{{ cluster_id }}" + } + ], + "ingress_vip": "{{ vips.ingress }}", + "ssh_public_key": "{{ ssh_authorized_key }}", + "vip_dhcp_allocation": false, + "network_type": "{{ network_type }}", + "machine_networks": [], + "cluster_networks": [ + { + "cidr": "10.128.0.0/14", + "host_prefix": 23 + } + ], + "service_networks": [ + { + "cidr": "172.30.0.0/16" + } + ], + "user_managed_networking": false +} diff --git a/roles/vmware_assisted_installer/defaults/main.yaml b/roles/vmware_assisted_installer/defaults/main.yaml new file mode 100644 index 0000000..a61fd64 --- /dev/null +++ b/roles/vmware_assisted_installer/defaults/main.yaml @@ -0,0 +1 @@ +validate_certs: true diff --git a/roles/vmware_assisted_installer/tasks/boot.yaml b/roles/vmware_assisted_installer/tasks/boot.yaml new file mode 100644 index 0000000..908b749 --- /dev/null +++ b/roles/vmware_assisted_installer/tasks/boot.yaml @@ -0,0 +1,104 @@ +- name: Generate ISO File Name + ansible.builtin.set_fact: + iso_file_name: "{{ cluster_name }}-{{ api_create_infra_envs_results.json.id }}-{{ discovery_iso_type }}.iso" + +- name: Download Discovery ISO + ansible.builtin.get_url: + dest: "{{ discovery_iso_download_path }}/{{ iso_file_name }}" + url: "{{ api_create_infra_envs_results.json.download_url }}" + mode: "0600" + +- name: Upload Discovery ISO to Datastore + community.vmware.vsphere_copy: + datacenter: "{{ vcenter_datacenter }}" + datastore: "{{ discovery_iso_datastore }}" + hostname: "{{ vcenter_hostname }}" + password: "{{ vcenter_password }}" + path: "{{ iso_file_name }}" + src: "{{ discovery_iso_download_path }}/{{ iso_file_name }}" + username: "{{ vcenter_username }}" + validate_certs: "{{ validate_certs }}" + +- name: Get VM Facts + community.vmware.vmware_vm_info: + hostname: "{{ vcenter_hostname }}" + password: "{{ vcenter_password }}" + username: "{{ vcenter_username }}" + validate_certs: "{{ validate_certs }}" + vm_name: "{{ item }}.{{ base_domain }}" + loop: "{{ groups[provision_group] }}" + register: vmware_vm_info_results + +- name: Debug vmware_vm_info_results + ansible.builtin.debug: + var: vmware_vm_info_results + +- name: Generate vCenter Session Token + ansible.builtin.uri: + body_format: json + method: POST + return_content: true + url: "https://{{ vcenter_hostname }}/rest/com/vmware/cis/session" + url_password: "{{ vcenter_password }}" + url_username: "{{ vcenter_username }}" + validate_certs: "{{ validate_certs }}" + register: vcenter_session_token_response + +- name: Set Session Token Fact + ansible.builtin.set_fact: + vcenter_session_token: "{{ vcenter_session_token_response.json.value }}" + +# Not supported in community.vmware collection +- name: Attach SATA Controller to VM Using vCenter REST API + ansible.builtin.uri: + body_format: json + body: + bus: 0 + pci_slot_number: 0 + type: AHCI + headers: + vmware-api-session-id: "{{ vcenter_session_token }}" + method: POST + return_content: true + status_code: + - 201 + url: "https://{{ vcenter_hostname }}/api/vcenter/vm/{{ item.virtual_machines[0].moid }}/hardware/adapter/sata" + validate_certs: "{{ validate_certs }}" + loop: "{{ vmware_vm_info_results.results }}" + +# Not supported in community.vmware collection +- name: Attach CD-ROM to SATA Controller Using vCenter REST API + ansible.builtin.uri: + body_format: json + body: + allow_guest_control: false + backing: + iso_file: "[{{ discovery_iso_datastore }}] {{ iso_file_name }}" + type: ISO_FILE + sata: + bus: 0 + unit: 0 + start_connected: true + type: SATA + headers: + vmware-api-session-id: "{{ vcenter_session_token }}" + method: POST + return_content: true + status_code: + - 201 + url: "https://{{ vcenter_hostname }}/api/vcenter/vm/{{ item.virtual_machines[0].moid }}/hardware/cdrom" + validate_certs: "{{ validate_certs }}" + loop: "{{ vmware_vm_info_results.results }}" + +- name: Boot VMs with Discovery ISO + community.vmware.vmware_guest: + cluster: "{{ vcenter_cluster }}" + datacenter: "{{ vcenter_datacenter }}" + datastore: "{{ hostvars[item].datastore }}" + hostname: "{{ vcenter_hostname }}" + name: "{{ item }}.{{ base_domain }}" + password: "{{ vcenter_password }}" + state: poweredon + username: "{{ vcenter_username }}" + validate_certs: "{{ validate_certs }}" + loop: "{{ groups[provision_group] }}" diff --git a/roles/vmware_assisted_installer/tasks/create.yaml b/roles/vmware_assisted_installer/tasks/create.yaml new file mode 100644 index 0000000..910b7b9 --- /dev/null +++ b/roles/vmware_assisted_installer/tasks/create.yaml @@ -0,0 +1,96 @@ +- name: Create Folder for Cluster {{ cluster_name }} + community.vmware.vcenter_folder: + datacenter_name: "{{ vcenter_datacenter }}" + folder_name: "{{ cluster_name }}" + folder_type: vm + hostname: "{{ vcenter_hostname }}" + password: "{{ vcenter_password }}" + state: present + username: "{{ vcenter_username }}" + validate_certs: "{{ validate_certs }}" + +- name: Create VMs + community.vmware.vmware_guest: + advanced_settings: + - key: "disk.EnableUUID" + value: "TRUE" + cluster: "{{ vcenter_cluster }}" + datacenter: "{{ vcenter_datacenter }}" + datastore: "{{ hostvars[item].datastore }}" + disk: "{{ hostvars[item].disk | default([]) | list }}" + folder: "/{{ vcenter_datacenter }}/vm/{{ cluster_name }}" + guest_id: rhel9_64Guest + hardware: + boot_firmware: efi + cpu_reservation: "{{ hostvars[item].cpu_reservation | default(0) }}" + memory_mb: "{{ hostvars[item].memory }}" + mem_reservation: "{{ hostvars[item].mem_reservation | default(0) }}" + nested_virt: "{{ hostvars[item].nested_virt | default(False) }}" + num_cpu_cores_per_socket: "{{ hostvars[item].cores }}" + num_cpus: "{{ hostvars[item].cores }}" + version: "{{ vcenter_hardware_version | int }}" + hostname: "{{ vcenter_hostname }}" + name: "{{ item }}.{{ base_domain }}" + networks: + - name: "{{ vcenter_network }}" + device_type: vmxnet3 + password: "{{ vcenter_password }}" + state: present + username: "{{ vcenter_username }}" + validate_certs: "{{ validate_certs }}" + loop: "{{ groups[provision_group] }}" + register: vmware_guest_results + +- name: Combine Applicable MACs & IPs into Dictionary for Easy Lookup + vars: + _query: "results[?instance.hw_name==`{{ item }}.{{ base_domain }}`].instance.hw_eth0.macaddress" + ansible.builtin.set_fact: + mac_ip_dict: >- + {{ + mac_ip_dict | default([]) + + [ + { + 'dns': hostvars[item].dns, + 'gateway': hostvars[item].gateway, + 'ip': hostvars[item].ip, + 'mac': vmware_guest_results | json_query(_query) | first, + 'name': item + } + ] + }} + loop: "{{ groups[provision_group] }}" + +- name: Combine Applicable Disks & Hostnames into Dictionary for Easy Lookup + ansible.builtin.set_fact: + disk_name_dict: >- + {{ + disk_name_dict | default([]) + + [ + { + 'name': item, + 'disks': hostvars[item].disks | default([]) + } + ] + }} + loop: "{{ groups[provision_group] }}" + +- name: Add Disks to VMs + community.vmware.vmware_guest_disk: + datacenter: "{{ vcenter_datacenter }}" + disk: + - controller_number: "{{ item.1.controller_number }}" + datastore: "{{ item.1.datastore }}" + size_gb: "{{ item.1.size_gb }}" + state: present + type: "{{ item.1.type }}" + unit_number: "{{ item.1.unit_number }}" + folder: "/{{ vcenter_datacenter }}/vm/{{ cluster_name }}" + hostname: "{{ vcenter_hostname }}" + name: "{{ item.0.name }}.{{ base_domain }}" + password: "{{ vcenter_password }}" + username: "{{ vcenter_username }}" + validate_certs: "{{ validate_certs }}" + with_subelements: + - "{{ disk_name_dict }}" + - disks + - skip_missing: false diff --git a/roles/vmware_assisted_installer/tasks/main.yaml b/roles/vmware_assisted_installer/tasks/main.yaml new file mode 100644 index 0000000..43fce6e --- /dev/null +++ b/roles/vmware_assisted_installer/tasks/main.yaml @@ -0,0 +1,11 @@ +- name: Import Create Tasks + ansible.builtin.import_tasks: + file: create.yaml + when: + - _vmware_assisted_installer_action == "create" + +- name: Import Boot Tasks + ansible.builtin.import_tasks: + file: boot.yaml + when: + - _vmware_assisted_installer_action == "boot"