diff --git a/.gitignore b/.gitignore index 24e3e9697..de0efa771 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,8 @@ scratch-test/ assets/templates/99_master-chronyd-redhat.yaml assets/templates/99_worker-chronyd-redhat.yaml -pull_secret.json \ No newline at end of file +pull_secret.json + +# Auto-generated ansible vars (may contain secrets) +ansible/.config_vars.yml +ansible/.runtime_vars.yml \ No newline at end of file diff --git a/05_create_install_config.sh b/05_create_install_config.sh index 967c8ab4b..0e80cedf3 100755 --- a/05_create_install_config.sh +++ b/05_create_install_config.sh @@ -14,22 +14,79 @@ early_deploy_validation set_api_and_ingress_vip if [ ! -f "${OCP_DIR}/install-config.yaml" ]; then - # Validate there are enough nodes to avoid confusing errors later.. NODES_LEN=$(jq '.nodes | length' "${NODES_FILE}") if (( NODES_LEN < NUM_MASTERS + NUM_WORKERS )); then echo "ERROR: ${NODES_FILE} contains ${NODES_LEN} nodes, but ${NUM_MASTERS} masters and ${NUM_WORKERS} workers requested" exit 1 fi - # Create a nodes.json file - mkdir -p "${OCP_DIR}" - jq '{nodes: .}' "${NODES_FILE}" | tee "${BAREMETALHOSTS_FILE}" + # Generate base config vars from the resolved bash environment + SCRIPTDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + "${SCRIPTDIR}/ansible/vars_bridge.sh" - # Create install config for openshift-installer - generate_ocp_install_config "${OCP_DIR}" + EXTRA_VARS_FILE="${SCRIPTDIR}/ansible/.runtime_vars.yml" + + # Append runtime-computed values that vars_bridge.sh does not cover + cat > "${EXTRA_VARS_FILE}" << RUNTIME_EOF +# Runtime vars generated by 05_create_install_config.sh +devscripts_machine_os_bootstrap_image_name: "${MACHINE_OS_BOOTSTRAP_IMAGE_NAME}" +devscripts_machine_os_bootstrap_image_sha256: "${MACHINE_OS_BOOTSTRAP_IMAGE_UNCOMPRESSED_SHA256}" +devscripts_nodes_file: "${NODES_FILE}" +devscripts_ocp_dir: "${OCP_DIR}" +devscripts_scriptdir: "${SCRIPTDIR}" +RUNTIME_EOF + + # BMC verify CA certificate (OCP >= 4.22) + BMC_CA_FILE="${WORKING_DIR}/virtualbmc/sushy-tools/cert.pem" + if [[ -f "${BMC_CA_FILE}" ]] && ! is_lower_version "$(openshift_version "${OCP_DIR}")" "4.22"; then + { + echo 'devscripts_bmc_verify_ca: |' + sed 's/^/ /' "${BMC_CA_FILE}" + } >> "${EXTRA_VARS_FILE}" + fi + + # Image mirror content sources and trust bundle + MIRROR_CONTENT=$(image_mirror_config || true) + if [[ -n "${MIRROR_CONTENT}" ]]; then + # Extract imageContentSources as JSON array for the template + ICS_YAML=$(echo "${MIRROR_CONTENT}" | sed -n '/^imageContentSources:/,/^[a-zA-Z]/p' | sed '$d') + if [[ -n "${ICS_YAML}" ]]; then + echo "${ICS_YAML}" >> "${EXTRA_VARS_FILE}" + fi + + # Extract additionalTrustBundle content + TRUST_BUNDLE=$(echo "${MIRROR_CONTENT}" | sed -n '/^additionalTrustBundle:/,$ p') + if [[ -n "${TRUST_BUNDLE}" ]]; then + { + echo "devscripts_additional_trust_bundle_content: |" + echo "${TRUST_BUNDLE}" | tail -n +2 + } >> "${EXTRA_VARS_FILE}" + fi + elif [[ -n "${ADDITIONAL_TRUST_BUNDLE:-}" ]]; then + { + echo "devscripts_additional_trust_bundle_content: |" + awk '{ print " ", $0 }' "${ADDITIONAL_TRUST_BUNDLE}" + } >> "${EXTRA_VARS_FILE}" + fi + + # Proxy variables (when INSTALLER_PROXY is set) + if [[ -n "${INSTALLER_PROXY:-}" ]]; then + cat >> "${EXTRA_VARS_FILE}" << PROXY_EOF +devscripts_http_proxy: "${HTTP_PROXY}" +devscripts_https_proxy: "${HTTPS_PROXY}" +devscripts_no_proxy: "${NO_PROXY}" +PROXY_EOF + fi + + ANSIBLE_FORCE_COLOR=true ansible-playbook \ + -i "${SCRIPTDIR}/ansible/inventory/hosts" \ + --extra-vars "@${SCRIPTDIR}/ansible/.config_vars.yml" \ + --extra-vars "@${EXTRA_VARS_FILE}" \ + -v \ + "${SCRIPTDIR}/ansible/playbooks/install_config.yml" fi -# Generate the assets for extra worker VMs +# Generate the assets for extra worker VMs (not yet migrated to Ansible) if [ -f "${EXTRA_NODES_FILE}" ]; then jq '.nodes' "${EXTRA_NODES_FILE}" | tee "${EXTRA_BAREMETALHOSTS_FILE}" generate_ocp_host_manifest "${OCP_DIR}" "${EXTRA_BAREMETALHOSTS_FILE}" extra_host_manifests.yaml "${EXTRA_WORKERS_NAMESPACE}" diff --git a/ansible/group_vars/all/main.yml b/ansible/group_vars/all/main.yml new file mode 100644 index 000000000..e720ce70b --- /dev/null +++ b/ansible/group_vars/all/main.yml @@ -0,0 +1,43 @@ +# Cluster identity +devscripts_cluster_name: ostest +devscripts_base_domain: test.metalkube.org +devscripts_working_dir: /opt/dev-scripts +devscripts_ssh_pub_key: "{{ lookup('file', ansible_env.HOME + '/.ssh/id_rsa.pub') }}" + +# Release image +# These mirror the bash defaults from common.sh. +# Set devscripts_release_image to pin an explicit image and skip resolution. +devscripts_release_stream: "4.22" +devscripts_release_type: nightly +devscripts_release_image: "" +devscripts_openshift_version: "" + +# CI mode — when true, skips pull-secret merge, uses mixed BMC driver, etc. +devscripts_openshift_ci: false + +# Security and features +devscripts_fips_mode: false +devscripts_feature_set: "" +devscripts_feature_gates: "" +devscripts_os_image_stream: "" + +# Capabilities +devscripts_baseline_capability_set: "" +devscripts_additional_capabilities: "" + +# Workload partitioning (set to true to add cpuPartitioningMode: AllNodes) +devscripts_enable_workload_partitioning: false + +# Node counts +devscripts_num_masters: 3 +devscripts_num_workers: 2 +devscripts_num_arbiters: 0 +devscripts_num_extra_workers: 0 + +# BMC driver: redfish, redfish-virtualmedia, ipmi, mixed +devscripts_bmc_driver: redfish + +# Misc +devscripts_disable_multicast: false +devscripts_enable_bootstrap_static_ip: false +devscripts_external_loadbalancer: false diff --git a/ansible/group_vars/all/network.yml b/ansible/group_vars/all/network.yml new file mode 100644 index 000000000..8e36d5713 --- /dev/null +++ b/ansible/group_vars/all/network.yml @@ -0,0 +1,82 @@ +# IP stack configuration +# Choices: v4, v6, v4v6, v6v4 +devscripts_ip_stack: v6 +devscripts_host_ip_stack: "{{ devscripts_ip_stack }}" + +# Provisioning network profile: Managed or Disabled +devscripts_provisioning_network_profile: Managed + +# Network names (derived from cluster name by default) +devscripts_provisioning_network_name: "{{ devscripts_cluster_name }}pr" +devscripts_baremetal_network_name: "{{ devscripts_cluster_name }}bm" + +# Provisioning network CIDR — defaults depend on host_ip_stack. +# Override explicitly or leave empty to use the ip-stack-based default. +devscripts_provisioning_network: >- + {%- if devscripts_host_ip_stack in ['v4', 'v4v6'] -%} + 172.22.0.0/24 + {%- else -%} + fd00:1101::0/64 + {%- endif -%} + +# External subnets — defaults depend on host_ip_stack. +# For single-stack, the unused family is left empty. +devscripts_external_subnet_v4: >- + {%- if devscripts_host_ip_stack in ['v4', 'v4v6', 'v6v4'] -%} + 192.168.111.0/24 + {%- else -%} + {%- endif -%} + +devscripts_external_subnet_v6: >- + {%- if devscripts_host_ip_stack in ['v6', 'v4v6', 'v6v4'] -%} + fd2e:6f44:5dd8:c956::/120 + {%- else -%} + {%- endif -%} + +# Cluster networking — defaults depend on ip_stack. +devscripts_cluster_subnet_v4: >- + {%- if devscripts_ip_stack in ['v4', 'v4v6', 'v6v4'] -%} + 10.128.0.0/14 + {%- else -%} + {%- endif -%} + +devscripts_cluster_subnet_v6: >- + {%- if devscripts_ip_stack in ['v6', 'v4v6', 'v6v4'] -%} + fd01::/48 + {%- else -%} + {%- endif -%} + +devscripts_cluster_host_prefix_v4: >- + {%- if devscripts_ip_stack in ['v4', 'v4v6', 'v6v4'] -%} + 23 + {%- else -%} + {%- endif -%} + +devscripts_cluster_host_prefix_v6: >- + {%- if devscripts_ip_stack in ['v6', 'v4v6', 'v6v4'] -%} + 64 + {%- else -%} + {%- endif -%} + +devscripts_service_subnet_v4: >- + {%- if devscripts_ip_stack in ['v4', 'v4v6', 'v6v4'] -%} + 172.30.0.0/16 + {%- else -%} + {%- endif -%} + +devscripts_service_subnet_v6: >- + {%- if devscripts_ip_stack in ['v6', 'v4v6', 'v6v4'] -%} + fd02::/112 + {%- else -%} + {%- endif -%} + +# Network type — OVNKubernetes is required for IPv6 and dual-stack. +# For IPv4-only on OCP < 4.15, OpenShiftSDN was the default. +devscripts_network_type: OVNKubernetes + +# Provisioning interface inside the cluster nodes +devscripts_cluster_provisioning_interface: enp1s0 + +# Proxy settings (set devscripts_installer_proxy to true to enable) +devscripts_installer_proxy: false +devscripts_installer_proxy_port: 8215 diff --git a/ansible/group_vars/all/registry.yml b/ansible/group_vars/all/registry.yml new file mode 100644 index 000000000..0ebb8987c --- /dev/null +++ b/ansible/group_vars/all/registry.yml @@ -0,0 +1,32 @@ +# Local registry — set devscripts_enable_local_registry to true to start one +devscripts_enable_local_registry: false + +# Registry backend: podman or quay +devscripts_registry_backend: podman +devscripts_registry_port: "5000" +devscripts_registry_user: ocp-user +devscripts_registry_pass: ocp-pass +devscripts_registry_dir: "{{ devscripts_working_dir }}/registry" +devscripts_registry_dns_name: "virthost.{{ devscripts_cluster_name }}.{{ devscripts_base_domain }}" +devscripts_registry_crt: registry.2.crt + +# Image mirroring — required for IPv6, optional for IPv4 +# When left empty, defaults to true for v6 ip_stack. +devscripts_mirror_images: >- + {%- if devscripts_ip_stack == 'v6' -%} + true + {%- else -%} + false + {%- endif -%} + +# Mirror command: oc-adm or oc-mirror +devscripts_mirror_command: oc-adm + +# OLM operators to mirror (comma-separated) +devscripts_mirror_olm: "" + +# Custom images to mirror (comma-separated) +devscripts_mirror_custom_images: "" + +# Additional trust bundle PEM file path (leave empty to skip) +devscripts_additional_trust_bundle: "" diff --git a/ansible/inventory/hosts b/ansible/inventory/hosts new file mode 100644 index 000000000..df8b5f69d --- /dev/null +++ b/ansible/inventory/hosts @@ -0,0 +1,2 @@ +[all] +localhost ansible_connection=local diff --git a/ansible/playbooks/install_config.yml b/ansible/playbooks/install_config.yml new file mode 100644 index 000000000..8f7a69bd3 --- /dev/null +++ b/ansible/playbooks/install_config.yml @@ -0,0 +1,16 @@ +--- +- name: Generate install-config.yaml + hosts: localhost + gather_facts: true + pre_tasks: + - name: Load nodes file + ansible.builtin.set_fact: + devscripts_nodes: "{{ (lookup('file', devscripts_nodes_file) | from_json).nodes }}" + + - name: Resolve registry credentials path + ansible.builtin.set_fact: + devscripts_registry_creds: >- + {{ ansible_env.HOME }}/private-mirror-{{ devscripts_cluster_name }}.json + + roles: + - role: "{{ playbook_dir }}/../roles/install_config" diff --git a/ansible/roles/install_config/defaults/main.yml b/ansible/roles/install_config/defaults/main.yml new file mode 100644 index 000000000..bda897109 --- /dev/null +++ b/ansible/roles/install_config/defaults/main.yml @@ -0,0 +1,73 @@ +# Role defaults — lowest precedence; overridden by group_vars or extra-vars. +# Variables not yet migrated to group_vars fall back to lookup('env'). + +# Paths derived from group_vars +devscripts_ocp_dir: "{{ devscripts_scriptdir }}/ocp/{{ devscripts_cluster_name }}" +devscripts_scriptdir: "{{ playbook_dir }}/../.." +devscripts_nodes_file: "{{ devscripts_working_dir }}/{{ devscripts_cluster_name }}/ironic_nodes.json" +devscripts_baremetalhosts_file: "{{ devscripts_ocp_dir }}/baremetalhosts.json" +devscripts_pull_secret_file: "{{ devscripts_working_dir }}/pull_secret.json" +devscripts_cluster_domain: "{{ devscripts_cluster_name }}.{{ devscripts_base_domain }}" + +# Computed network values +devscripts_provisioning_host_external_ip: >- + {% if devscripts_host_ip_stack in ['v6', 'v6v4'] -%} + {{ devscripts_external_subnet_v6 | ansible.utils.nthhost(1) }} + {%- else -%} + {{ devscripts_external_subnet_v4 | ansible.utils.nthhost(1) }} + {%- endif %} +devscripts_mirror_ip: "{{ devscripts_provisioning_host_external_ip }}" + +# DNS VIP +devscripts_dns_vip: >- + {% if devscripts_ip_stack == 'v6' -%} + {{ devscripts_external_subnet_v6 | ansible.utils.nthhost(2) }} + {%- else -%} + {{ devscripts_external_subnet_v4 | ansible.utils.nthhost(2) }} + {%- endif %} + +# VIPs — will be resolved in tasks from libvirt DNS or computed from subnets +devscripts_api_vips: [] +devscripts_ingress_vips: [] + +# Bootstrap static IP (.9 on the primary external subnet) +devscripts_bootstrap_static_ip: >- + {% if devscripts_enable_bootstrap_static_ip -%} + {% if devscripts_ip_stack in ['v6', 'v6v4'] -%} + {{ devscripts_external_subnet_v6 | ansible.utils.nthhost(9) }} + {%- else -%} + {{ devscripts_external_subnet_v4 | ansible.utils.nthhost(9) }} + {%- endif %} + {%- endif %} + +# Provisioning IPs +devscripts_bootstrap_provisioning_ip: >- + {% if devscripts_provisioning_network_profile == 'Disabled' -%} + {% if devscripts_host_ip_stack == 'v6' -%} + {{ devscripts_external_subnet_v6 | ansible.utils.nthhost(7) }} + {%- else -%} + {{ devscripts_external_subnet_v4 | ansible.utils.nthhost(7) }} + {%- endif %} + {%- else -%} + {{ devscripts_provisioning_network | ansible.utils.nthhost(2) }} + {%- endif %} + +devscripts_cluster_provisioning_ip: >- + {% if devscripts_provisioning_network_profile == 'Disabled' -%} + {% if devscripts_host_ip_stack == 'v6' -%} + {{ devscripts_external_subnet_v6 | ansible.utils.nthhost(8) }} + {%- else -%} + {{ devscripts_external_subnet_v4 | ansible.utils.nthhost(8) }} + {%- endif %} + {%- else -%} + {{ devscripts_provisioning_network | ansible.utils.nthhost(3) }} + {%- endif %} + +# Not yet migrated — fall back to environment variables +devscripts_root_disk_name: "{{ lookup('env', 'ROOT_DISK_NAME') | default('/dev/sda', true) }}" +devscripts_remote_libvirt: "{{ lookup('env', 'REMOTE_LIBVIRT') | default('0', true) }}" +devscripts_provisioning_host_user: "{{ lookup('env', 'PROVISIONING_HOST_USER') | default(ansible_user_id, true) }}" +devscripts_provisioning_host_ip: "{{ lookup('env', 'PROVISIONING_HOST_IP') | default(devscripts_provisioning_network | ansible.utils.nthhost(1), true) }}" +devscripts_network_config_folder: "{{ lookup('env', 'NETWORK_CONFIG_FOLDER') | default('', true) }}" +devscripts_enable_two_node_fencing: "{{ lookup('env', 'ENABLE_TWO_NODE_FENCING') | default('false', true) | bool }}" +devscripts_master_hostname_format: "{{ lookup('env', 'MASTER_HOSTNAME_FORMAT') | default('master-%d', true) }}" diff --git a/ansible/roles/install_config/filter_plugins/host_entries.py b/ansible/roles/install_config/filter_plugins/host_entries.py new file mode 100644 index 000000000..0e5651344 --- /dev/null +++ b/ansible/roles/install_config/filter_plugins/host_entries.py @@ -0,0 +1,63 @@ +"""Filter to convert ironic nodes JSON entries into install-config host dicts.""" + +from __future__ import annotations + +from typing import Any + + +def to_host_entries(nodes: list[dict[str, Any]], role: str) -> list[dict[str, Any]]: + """Map a list of ironic node dicts to install-config host entries. + + Each node in the ironic_nodes.json has the structure: + { + "name": "...", + "ports": [{"address": "mac"}], + "driver": "ipmi|redfish|idrac", + "driver_info": { + "username": "...", + "password": "...", + "address": "...", + "redfish_verify_ca": "True|False", + }, + "properties": { + "boot_mode": "uefi|bios|null", + "cpu_arch": "x86_64|aarch64", + }, + } + """ + result = [] + for node in nodes: + entry: dict[str, Any] = { + "name": node["name"], + "role": role, + "mac": node["ports"][0]["address"], + "boot_mode": _boot_mode(node), + "bmc_address": node["driver_info"]["address"], + "bmc_username": node["driver_info"]["username"], + "bmc_password": node["driver_info"]["password"], + } + + driver = node.get("driver", "redfish") + if driver not in ("ipmi", "idrac"): + verify_ca = node["driver_info"].get("redfish_verify_ca", "True") + if str(verify_ca).lower() == "false": + entry["disable_certificate_verification"] = True + + result.append(entry) + return result + + +def _boot_mode(node: dict[str, Any]) -> str: + raw = node.get("properties", {}).get("boot_mode") + if raw and raw != "null": + return raw.upper() + return "UEFI" + + +class FilterModule: + """Ansible filter plugin registration.""" + + def filters(self) -> dict[str, Any]: + return { + "devscripts_to_host_entries": to_host_entries, + } diff --git a/ansible/roles/install_config/tasks/main.yml b/ansible/roles/install_config/tasks/main.yml new file mode 100644 index 000000000..2033daa4a --- /dev/null +++ b/ansible/roles/install_config/tasks/main.yml @@ -0,0 +1,119 @@ +--- +- name: Validate node count + ansible.builtin.assert: + that: + - devscripts_nodes | length >= (devscripts_num_masters | int) + (devscripts_num_workers | int) + fail_msg: >- + {{ devscripts_nodes_file }} contains {{ devscripts_nodes | length }} nodes, + but {{ devscripts_num_masters }} masters and {{ devscripts_num_workers }} workers requested + +- name: Detect architecture + ansible.builtin.set_fact: + devscripts_arch: "{{ 'arm64' if ansible_architecture == 'aarch64' else 'amd64' }}" + +- name: Create OCP directory + ansible.builtin.file: + path: "{{ devscripts_ocp_dir }}" + state: directory + mode: "0755" + +- name: Write baremetalhosts.json + ansible.builtin.copy: + content: "{{ {'nodes': devscripts_nodes} | to_nice_json }}" + dest: "{{ devscripts_baremetalhosts_file }}" + mode: "0644" + +- name: Build install hosts list + ansible.builtin.set_fact: + devscripts_install_hosts: "{{ _masters + _arbiters + _workers }}" + vars: + _masters: >- + {{ devscripts_nodes[:devscripts_num_masters | int] | devscripts_to_host_entries('master') }} + _arbiters: >- + {{ devscripts_nodes[devscripts_num_masters | int:(devscripts_num_masters | int + devscripts_num_arbiters | int)] + | devscripts_to_host_entries('arbiter') }} + _workers: >- + {{ devscripts_nodes[(devscripts_num_masters | int + devscripts_num_arbiters | int): + (devscripts_num_masters | int + devscripts_num_arbiters | int + devscripts_num_workers | int)] + | devscripts_to_host_entries('worker') }} + +- name: Resolve VIPs from subnets + ansible.builtin.set_fact: + devscripts_api_vips: "{{ _api_vips | select | list }}" + devscripts_ingress_vips: "{{ _ingress_vips | select | list }}" + vars: + _api_v4: >- + {{ devscripts_external_subnet_v4 | ansible.utils.nthhost(5) + if devscripts_external_subnet_v4 else '' }} + _api_v6: >- + {{ devscripts_external_subnet_v6 | ansible.utils.nthhost(5) + if devscripts_external_subnet_v6 else '' }} + _ingress_offset: "{{ 1 if devscripts_external_loadbalancer | bool else 4 }}" + _ingress_v4: >- + {{ devscripts_external_subnet_v4 | ansible.utils.nthhost(_ingress_offset | int) + if devscripts_external_subnet_v4 else '' }} + _ingress_v6: >- + {{ devscripts_external_subnet_v6 | ansible.utils.nthhost(_ingress_offset | int) + if devscripts_external_subnet_v6 else '' }} + _api_vips: >- + {% if devscripts_ip_stack in ['v4', 'v4v6'] -%} + {{ [_api_v4, _api_v6] }} + {%- else -%} + {{ [_api_v6, _api_v4] }} + {%- endif %} + _ingress_vips: >- + {% if devscripts_ip_stack in ['v4', 'v4v6'] -%} + {{ [_ingress_v4, _ingress_v6] }} + {%- else -%} + {{ [_ingress_v6, _ingress_v4] }} + {%- endif %} + +- name: Load pull secret + ansible.builtin.set_fact: + devscripts_pull_secret_content: >- + {{ lookup('file', _pull_secret_file) | from_json | to_json }} + vars: + _pull_secret_file: >- + {{ devscripts_registry_creds + if devscripts_mirror_images | bool + else devscripts_pull_secret_file }} + no_log: true + +- name: Build fencing credentials + when: devscripts_enable_two_node_fencing | bool + ansible.builtin.set_fact: + devscripts_fencing_credentials: "{{ _creds }}" + vars: + _creds: >- + {% set result = [] -%} + {% for idx in range(devscripts_num_masters | int) -%} + {% set node = devscripts_nodes[idx] -%} + {% set hostname = devscripts_master_hostname_format | format(idx) -%} + {% set fqdn = hostname + '.' + devscripts_cluster_domain + if devscripts_ip_stack != 'v4' else hostname -%} + {% set _ = result.append({ + 'hostname': fqdn, + 'address': node.driver_info.address, + 'username': node.driver_info.username, + 'password': node.driver_info.password, + }) -%} + {% endfor -%} + {{ result }} + +- name: Set empty fencing credentials + when: not (devscripts_enable_two_node_fencing | bool) + ansible.builtin.set_fact: + devscripts_fencing_credentials: [] + +- name: Render install-config.yaml + ansible.builtin.template: + src: install-config.yaml.j2 + dest: "{{ devscripts_ocp_dir }}/install-config.yaml" + mode: "0644" + +- name: Save install-config.yaml backup + ansible.builtin.copy: + src: "{{ devscripts_ocp_dir }}/install-config.yaml" + dest: "{{ devscripts_ocp_dir }}/install-config.yaml.save" + remote_src: true + mode: "0644" diff --git a/ansible/roles/install_config/templates/install-config.yaml.j2 b/ansible/roles/install_config/templates/install-config.yaml.j2 new file mode 100644 index 000000000..bdf6521fd --- /dev/null +++ b/ansible/roles/install_config/templates/install-config.yaml.j2 @@ -0,0 +1,185 @@ +apiVersion: v1 +baseDomain: {{ devscripts_base_domain }} +{% if devscripts_enable_workload_partitioning | bool %} +cpuPartitioningMode: AllNodes +{% endif %} +networking: + networkType: {{ devscripts_network_type }} +{% if devscripts_ip_stack == 'v4' %} + machineNetwork: + - cidr: {{ devscripts_external_subnet_v4 }} + clusterNetwork: + - cidr: {{ devscripts_cluster_subnet_v4 }} + hostPrefix: {{ devscripts_cluster_host_prefix_v4 }} + serviceNetwork: + - {{ devscripts_service_subnet_v4 }} +{% elif devscripts_ip_stack == 'v6' %} + machineNetwork: + - cidr: {{ devscripts_external_subnet_v6 }} + clusterNetwork: + - cidr: {{ devscripts_cluster_subnet_v6 }} + hostPrefix: {{ devscripts_cluster_host_prefix_v6 }} + serviceNetwork: + - {{ devscripts_service_subnet_v6 }} +{% elif devscripts_ip_stack == 'v4v6' %} + machineNetwork: + - cidr: {{ devscripts_external_subnet_v4 }} + - cidr: {{ devscripts_external_subnet_v6 }} + clusterNetwork: + - cidr: {{ devscripts_cluster_subnet_v4 }} + hostPrefix: {{ devscripts_cluster_host_prefix_v4 }} + - cidr: {{ devscripts_cluster_subnet_v6 }} + hostPrefix: {{ devscripts_cluster_host_prefix_v6 }} + serviceNetwork: + - {{ devscripts_service_subnet_v4 }} + - {{ devscripts_service_subnet_v6 }} +{% elif devscripts_ip_stack == 'v6v4' %} + machineNetwork: + - cidr: {{ devscripts_external_subnet_v6 }} + - cidr: {{ devscripts_external_subnet_v4 }} + clusterNetwork: + - cidr: {{ devscripts_cluster_subnet_v6 }} + hostPrefix: {{ devscripts_cluster_host_prefix_v6 }} + - cidr: {{ devscripts_cluster_subnet_v4 }} + hostPrefix: {{ devscripts_cluster_host_prefix_v4 }} + serviceNetwork: + - {{ devscripts_service_subnet_v6 }} + - {{ devscripts_service_subnet_v4 }} +{% endif %} +metadata: + name: {{ devscripts_cluster_name }} +compute: +- name: worker + replicas: {{ devscripts_num_workers }} + architecture: {{ devscripts_arch }} +controlPlane: + name: master + replicas: {{ devscripts_num_masters }} + architecture: {{ devscripts_arch }} + platform: + baremetal: {} +{% if devscripts_enable_two_node_fencing | bool and devscripts_fencing_credentials | length > 0 %} + fencing: + credentials: +{% for cred in devscripts_fencing_credentials %} + - hostname: {{ cred.hostname }} + address: {{ cred.address }} + username: {{ cred.username }} + password: {{ cred.password }} +{% if cred.certificate_verification is defined %} + certificateVerification: {{ cred.certificate_verification }} +{% endif %} +{% endfor %} +{% endif %} +{% if devscripts_num_arbiters | int > 0 %} +arbiter: + name: arbiter + replicas: {{ devscripts_num_arbiters }} + hyperthreading: Enabled + architecture: {{ devscripts_arch }} + platform: + baremetal: {} +{% endif %} +{% if devscripts_feature_set %} +featureSet: "{{ devscripts_feature_set }}" +{% endif %} +{% if devscripts_feature_gates %} +featureGates: +{% for gate in devscripts_feature_gates.split(',') %} +- {{ gate }} +{% endfor %} +{% endif %} +{% if devscripts_os_image_stream %} +osImageStream: "{{ devscripts_os_image_stream }}" +{% endif %} +{% if devscripts_baseline_capability_set %} +capabilities: + baselineCapabilitySet: "{{ devscripts_baseline_capability_set }}" +{% if devscripts_additional_capabilities %} + additionalEnabledCapabilities: +{% for cap in devscripts_additional_capabilities.split(',') %} + - {{ cap }} +{% endfor %} +{% endif %} +{% endif %} +platform: + baremetal: +{% if devscripts_remote_libvirt | int != 0 %} + libvirtURI: qemu+ssh://{{ devscripts_provisioning_host_user }}@{{ devscripts_provisioning_host_ip | ansible.utils.ipwrap }}/system +{% endif %} +{% if devscripts_provisioning_network_profile == 'Disabled' %} + provisioningNetwork: "{{ devscripts_provisioning_network_profile }}" + provisioningHostIP: "{{ devscripts_cluster_provisioning_ip }}" + bootstrapProvisioningIP: "{{ devscripts_bootstrap_provisioning_ip }}" +{% else %} + provisioningBridge: {{ devscripts_provisioning_network_name }} + provisioningNetworkCIDR: {{ devscripts_provisioning_network }} + provisioningNetworkInterface: {{ devscripts_cluster_provisioning_interface }} +{% endif %} +{% if devscripts_enable_bootstrap_static_ip | bool %} + bootstrapExternalStaticIP: "{{ devscripts_bootstrap_static_ip }}" + bootstrapExternalStaticGateway: "{{ devscripts_provisioning_host_external_ip }}" +{% endif %} + externalBridge: {{ devscripts_baremetal_network_name }} + bootstrapOSImage: http://{{ devscripts_mirror_ip | ansible.utils.ipwrap }}/images/{{ devscripts_machine_os_bootstrap_image_name }}?sha256={{ devscripts_machine_os_bootstrap_image_sha256 }} +{% if devscripts_api_vips | length > 0 %} + apiVIPs: +{% for vip in devscripts_api_vips %} + - {{ vip }} +{% endfor %} +{% endif %} +{% if devscripts_ingress_vips | length > 0 %} + ingressVIPs: +{% for vip in devscripts_ingress_vips %} + - {{ vip }} +{% endfor %} +{% endif %} +{% if devscripts_external_loadbalancer | bool %} + loadBalancer: + type: UserManaged +{% endif %} + hosts: +{% for host in devscripts_install_hosts %} + - name: {{ host.name }} + role: {{ host.role }} + bootMACAddress: {{ host.mac }} + bootMode: {{ host.boot_mode | default('UEFI') }} + bmc: + address: {{ host.bmc_address }} + username: {{ host.bmc_username }} + password: {{ host.bmc_password }} +{% if host.disable_certificate_verification is defined %} + disableCertificateVerification: {{ host.disable_certificate_verification }} +{% endif %} +{% if devscripts_network_config_folder and host.network_config is defined %} +{{ host.network_config | indent(8, first=true) }} +{% endif %} + rootDeviceHints: + deviceName: "{{ devscripts_root_disk_name }}" +{% if host.role in ['master', 'arbiter'] %} + hardwareProfile: default +{% else %} + hardwareProfile: unknown +{% endif %} +{% endfor %} +{% if devscripts_bmc_verify_ca is defined and devscripts_bmc_verify_ca | trim %} + bmcVerifyCA: | +{{ devscripts_bmc_verify_ca | indent(6, first=true) }} +{% endif %} +{% if imageContentSources is defined %} +{{ imageContentSources }} +{% endif %} +{% if devscripts_additional_trust_bundle_content is defined and devscripts_additional_trust_bundle_content | trim %} +additionalTrustBundle: | +{{ devscripts_additional_trust_bundle_content | indent(2, first=true) }} +{% endif %} +pullSecret: | + {{ devscripts_pull_secret_content }} +sshKey: "{{ devscripts_ssh_pub_key | trim }}" +fips: {{ devscripts_fips_mode | bool | lower }} +{% if devscripts_installer_proxy | bool %} +proxy: + httpProxy: {{ devscripts_http_proxy }} + httpsProxy: {{ devscripts_https_proxy }} + noProxy: {{ devscripts_no_proxy }} +{% endif %} diff --git a/ansible/vars_bridge.sh b/ansible/vars_bridge.sh new file mode 100755 index 000000000..7b80f02b7 --- /dev/null +++ b/ansible/vars_bridge.sh @@ -0,0 +1,122 @@ +#!/usr/bin/env bash +# +# Bridges the existing config.sh into Ansible extra-vars YAML. +# +# Sources common.sh and network.sh to resolve all defaults (including +# IP-stack-dependent values), then writes a YAML file that can be +# passed to ansible-playbook via --extra-vars @file. +# +# Usage: +# ./ansible/vars_bridge.sh [output_file] +# +# Default output: ansible/.config_vars.yml +# +set -euo pipefail + +[[ "${DEBUG:-}" == "true" ]] && set -x + +SCRIPTDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +# Source the full dev-scripts config chain +# shellcheck source=/dev/null +source "${SCRIPTDIR}/common.sh" +# shellcheck source=/dev/null +source "${SCRIPTDIR}/network.sh" + +OUTPUT="${1:-${SCRIPTDIR}/ansible/.config_vars.yml}" + +quote_yaml() { + local val="${1:-}" + if [[ -z "${val}" ]]; then + echo '""' + elif [[ "${val}" =~ ^(true|false|[0-9]+)$ ]]; then + echo "${val}" + else + echo "\"${val//\"/\\\"}\"" + fi +} + +cat > "${OUTPUT}" << ENDOFVARS +# Auto-generated by vars_bridge.sh — do not edit manually. +# Re-generate with: ./ansible/vars_bridge.sh +# +# This file is passed to ansible-playbook as --extra-vars @file +# and overrides group_vars defaults with the user's config.sh values. + +# Cluster identity +devscripts_cluster_name: $(quote_yaml "${CLUSTER_NAME}") +devscripts_base_domain: $(quote_yaml "${BASE_DOMAIN}") +devscripts_working_dir: $(quote_yaml "${WORKING_DIR}") +devscripts_ssh_pub_key: $(quote_yaml "${SSH_PUB_KEY}") + +# Release +devscripts_release_stream: $(quote_yaml "${OPENSHIFT_RELEASE_STREAM}") +devscripts_release_type: $(quote_yaml "${OPENSHIFT_RELEASE_TYPE}") +devscripts_release_image: $(quote_yaml "${OPENSHIFT_RELEASE_IMAGE}") +devscripts_openshift_version: $(quote_yaml "${OPENSHIFT_VERSION}") +devscripts_openshift_ci: ${OPENSHIFT_CI:-false} + +# Features +devscripts_fips_mode: ${FIPS_MODE:-false} +devscripts_feature_set: $(quote_yaml "${FEATURE_SET:-}") +devscripts_feature_gates: $(quote_yaml "${FEATURE_GATES:-}") +devscripts_os_image_stream: $(quote_yaml "${OS_IMAGE_STREAM:-}") + +# Capabilities +devscripts_baseline_capability_set: $(quote_yaml "${BASELINE_CAPABILITY_SET:-}") +devscripts_additional_capabilities: $(quote_yaml "${ADDITIONAL_CAPABILITIES:-}") + +# Workload partitioning +devscripts_enable_workload_partitioning: $([[ -n "${ENABLE_WORKLOAD_PARTITIONING:-}" ]] && echo "true" || echo "false") + +# Node counts +devscripts_num_masters: ${NUM_MASTERS} +devscripts_num_workers: ${NUM_WORKERS} +devscripts_num_arbiters: ${NUM_ARBITERS} +devscripts_num_extra_workers: ${NUM_EXTRA_WORKERS} + +# BMC +devscripts_bmc_driver: $(quote_yaml "${BMC_DRIVER}") + +# Misc +devscripts_disable_multicast: ${DISABLE_MULTICAST} +devscripts_enable_bootstrap_static_ip: $([[ -n "${ENABLE_BOOTSTRAP_STATIC_IP}" ]] && echo "true" || echo "false") +devscripts_external_loadbalancer: $([[ -n "${EXTERNAL_LOADBALANCER}" ]] && echo "true" || echo "false") + +# Network +devscripts_ip_stack: $(quote_yaml "${IP_STACK}") +devscripts_host_ip_stack: $(quote_yaml "${HOST_IP_STACK}") +devscripts_provisioning_network_profile: $(quote_yaml "${PROVISIONING_NETWORK_PROFILE}") +devscripts_provisioning_network_name: $(quote_yaml "${PROVISIONING_NETWORK_NAME}") +devscripts_baremetal_network_name: $(quote_yaml "${BAREMETAL_NETWORK_NAME}") +devscripts_provisioning_network: $(quote_yaml "${PROVISIONING_NETWORK}") +devscripts_external_subnet_v4: $(quote_yaml "${EXTERNAL_SUBNET_V4}") +devscripts_external_subnet_v6: $(quote_yaml "${EXTERNAL_SUBNET_V6}") +devscripts_cluster_subnet_v4: $(quote_yaml "${CLUSTER_SUBNET_V4}") +devscripts_cluster_subnet_v6: $(quote_yaml "${CLUSTER_SUBNET_V6}") +devscripts_cluster_host_prefix_v4: $(quote_yaml "${CLUSTER_HOST_PREFIX_V4}") +devscripts_cluster_host_prefix_v6: $(quote_yaml "${CLUSTER_HOST_PREFIX_V6}") +devscripts_service_subnet_v4: $(quote_yaml "${SERVICE_SUBNET_V4}") +devscripts_service_subnet_v6: $(quote_yaml "${SERVICE_SUBNET_V6}") +devscripts_network_type: $(quote_yaml "${NETWORK_TYPE}") +devscripts_cluster_provisioning_interface: $(quote_yaml "${CLUSTER_PRO_IF}") +devscripts_installer_proxy: $([[ -n "${INSTALLER_PROXY}" ]] && echo "true" || echo "false") +devscripts_installer_proxy_port: ${INSTALLER_PROXY_PORT} + +# Registry / mirror +devscripts_enable_local_registry: $([[ -n "${ENABLE_LOCAL_REGISTRY}" ]] && echo "true" || echo "false") +devscripts_registry_backend: $(quote_yaml "${REGISTRY_BACKEND}") +devscripts_registry_port: $(quote_yaml "${LOCAL_REGISTRY_PORT}") +devscripts_registry_user: $(quote_yaml "${REGISTRY_USER}") +devscripts_registry_pass: $(quote_yaml "${REGISTRY_PASS}") +devscripts_registry_dir: $(quote_yaml "${REGISTRY_DIR}") +devscripts_registry_dns_name: $(quote_yaml "${LOCAL_REGISTRY_DNS_NAME}") +devscripts_registry_crt: $(quote_yaml "${REGISTRY_CRT}") +devscripts_mirror_images: $([[ -n "${MIRROR_IMAGES}" && "${MIRROR_IMAGES,,}" != "false" ]] && echo "true" || echo "false") +devscripts_mirror_command: $(quote_yaml "${MIRROR_COMMAND}") +devscripts_mirror_olm: $(quote_yaml "${MIRROR_OLM:-}") +devscripts_mirror_custom_images: $(quote_yaml "${MIRROR_CUSTOM_IMAGES:-}") +devscripts_additional_trust_bundle: $(quote_yaml "${ADDITIONAL_TRUST_BUNDLE:-}") +ENDOFVARS + +echo "Config bridge: wrote ${OUTPUT}" >&2