diff --git a/images/capi/hack/windows-ova-unattend.py b/images/capi/hack/windows-ova-unattend.py index 818dd8c312..f12863c8f2 100755 --- a/images/capi/hack/windows-ova-unattend.py +++ b/images/capi/hack/windows-ova-unattend.py @@ -15,15 +15,102 @@ # limitations under the License. +import io import xml.etree.ElementTree as ET import os import argparse import json +ADDRESSES_TEMPLATE = """ %(cidr)s +""" +INTERFACE_COMPONENT_TEMPLATE = """ + + + + + false + + + false + + %(iface_id)s + +%(addresses)s + %(routes)s + + + + +""" +ROUTE_TEMPLATE = """ + + %(id)s + %(prefix)s + %(gateway)s + """ +DNS_TEMPLATE = """ + + %(interfaces)s + + +""" +DNS_INTERFACE_TEMPLATE = """ + + %(iface_id)s + %(dns_search_orders)s + + +""" +DNS_SEARCH_ORDER_TEMPLATE = """ + %(server)s""" +INTERFACE_ID = "Ethernet0" + + def set_xmlstring(root, location, key, value): - setting = root.find(location) - setting.find(key).text = value - return setting + setting = root.find(location) + setting.find(key).text = value + return setting + + +def ensure_interfaces(root, interfaces_content): + setting = root.find("*[@pass='specialize']") + old_element = setting.find(".//*[@name='Microsoft-Windows-TCPIP']") + modified = False + if old_element: + setting.remove(old_element) + modified = not interfaces_content + + if interfaces_content: + interface_component_tree = ET.parse(io.StringIO(interfaces_content)) + new_element = interface_component_tree.getroot() + setting.append(new_element) + modified = True + + return setting, modified + + +def ensure_dns_settings(root, dns_content): + setting = root.find("*[@pass='specialize']") + old_element = setting.find(".//*[@name='Microsoft-Windows-DNS-Client']") + modified = False + if old_element: + setting.remove(old_element) + modified = not dns_content + + if dns_content: + dns_component_tree = ET.parse(io.StringIO(dns_content)) + new_element = dns_component_tree.getroot() + setting.append(new_element) + modified = True + + return setting, modified + def main(): parser = argparse.ArgumentParser( @@ -55,32 +142,86 @@ def main(): with open(args.var_file, 'r') as f: data = json.load(f) - modified=0 + modified = False os.chdir(args.build_dir) - unattend=ET.parse(args.unattend_file) + unattend = ET.parse(args.unattend_file) ET.register_namespace('', "urn:schemas-microsoft-com:unattend") ET.register_namespace('wcm', "http://schemas.microsoft.com/WMIConfig/2002/State") ET.register_namespace('xsi', "http://www.w3.org/2001/XMLSchema-instance") - + root = unattend.getroot() if data.get("unattend_timezone"): - modified=1 - setting = set_xmlstring(root, ".//*[@pass='oobeSystem']/*[@name='Microsoft-Windows-Shell-Setup']",'{urn:schemas-microsoft-com:unattend}TimeZone', data["unattend_timezone"]) - print("windows-ova-unattend: Setting Timezone to %s" % data["unattend_timezone"]) - + modified = True + setting = set_xmlstring(root, ".//*[@pass='oobeSystem']/*[@name='Microsoft-Windows-Shell-Setup']", + '{urn:schemas-microsoft-com:unattend}TimeZone', data["unattend_timezone"]) + print("windows-ova-unattend: Setting Timezone to %s" % data["unattend_timezone"]) + admin_password = data.get("admin_password") if admin_password: - modified=1 - set_xmlstring(root, ".//*[@pass='oobeSystem']/*[@name='Microsoft-Windows-Shell-Setup']/{*}UserAccounts/{*}AdministratorPassword",'{urn:schemas-microsoft-com:unattend}Value', admin_password) - set_xmlstring(root, ".//*[@pass='oobeSystem']/*[@name='Microsoft-Windows-Shell-Setup']/{*}AutoLogon/{*}Password",'{urn:schemas-microsoft-com:unattend}Value', admin_password) - print("windows-ova-unattend: Setting Administrator Password") - - if modified == 1: - print("windows-ova-unattend: Updating %s ..." % args.unattend_file) - unattend.write(args.unattend_file) + modified = True + set_xmlstring(root, + ".//*[@pass='oobeSystem']/*[@name='Microsoft-Windows-Shell-Setup']/{*}UserAccounts/{*}AdministratorPassword", + '{urn:schemas-microsoft-com:unattend}Value', admin_password) + set_xmlstring(root, + ".//*[@pass='oobeSystem']/*[@name='Microsoft-Windows-Shell-Setup']/{*}AutoLogon/{*}Password", + '{urn:schemas-microsoft-com:unattend}Value', admin_password) + print("windows-ova-unattend: Setting Administrator Password") + + addr_elements = [] + ip_addr_cidr = data.get("ipv4_address_cidr") + gateway4 = data.get("gateway4") + if ip_addr_cidr: + modified = True + route_configs = [] + if gateway4: + route_configs.append(("0.0.0.0/0", gateway4)) + + routes = [] + for i in range(len(route_configs)): + route_config = route_configs[i] + routes.append(ROUTE_TEMPLATE % {"id": i + 1, "prefix": route_config[0], "gateway": route_config[1]}) + print("windows-ova-unattend: Setting Gateway to %s" % route_config[1]) + + addrs = [ip_addr_cidr] + for i in range(len(addrs)): + addr_elements.append(ADDRESSES_TEMPLATE % {"order": i + 1, + "cidr": addrs[i]}) + print("windows-ova-unattend: Setting IP Address to %s" % ip_addr_cidr) + + interfaces_content = None + if addr_elements: + interfaces_content = INTERFACE_COMPONENT_TEMPLATE % {"iface_id": INTERFACE_ID, + "addresses": ''.join(addr_elements), + "routes": ''.join(routes)} + + setting, xml_modified = ensure_interfaces(root, interfaces_content) + if xml_modified: + modified = True + + dns_servers = data.get("dns_servers") + dns_servers_content = '' + if dns_servers: + dns_servers = dns_servers.split() + search_order_content = [] + for i in range(len(dns_servers)): + search_order_content.append(DNS_SEARCH_ORDER_TEMPLATE % {"key": i + 1, + "server": dns_servers[i]}) + dns_interface_content = [DNS_INTERFACE_TEMPLATE % {"iface_id": INTERFACE_ID, + "dns_search_orders": ''.join(search_order_content)}] + dns_servers_content = DNS_TEMPLATE % {"interfaces": ''.join(dns_interface_content)} + print("windows-ova-unattend: Setting DNS Addresses to %s" % dns_servers) + + setting, xml_modified = ensure_dns_settings(root, dns_servers_content) + if xml_modified: + modified = True + + if modified: + print("windows-ova-unattend: Updating %s ..." % args.unattend_file) + unattend.write(args.unattend_file) else: - print("windows-ova-unattend: skipping...") + print("windows-ova-unattend: skipping...") + if __name__ == "__main__": main() diff --git a/images/capi/packer/ova/packer-common.json.tmpl b/images/capi/packer/ova/packer-common.json.tmpl index 16ea368193..602c88cf7f 100644 --- a/images/capi/packer/ova/packer-common.json.tmpl +++ b/images/capi/packer/ova/packer-common.json.tmpl @@ -7,13 +7,16 @@ "disk_controller_type": "pvscsi", "disk_thin_provisioned": "true", "disk_type_id": "0", + "dns_servers": null, "firmware": "bios", "format": "", + "gateway4": null, "guestinfo_datasource_ref": "v1.4.1", "guestinfo_datasource_script": "{{user `guestinfo_datasource_slug`}}/{{user `guestinfo_datasource_ref`}}/install.sh", "guestinfo_datasource_slug": "https://raw.githubusercontent.com/vmware/cloud-init-vmware-guestinfo", "headless": "true", "insecure_connection": "false", + "ipv4_address_cidr": null, "memory": "8192", "network": "", "network_card": "vmxnet3", diff --git a/images/capi/packer/ova/packer-windows.json b/images/capi/packer/ova/packer-windows.json index 52eec19894..21aeda6651 100644 --- a/images/capi/packer/ova/packer-windows.json +++ b/images/capi/packer/ova/packer-windows.json @@ -1,7 +1,7 @@ { "builders": [ { - "content": "{\n \"unattend_timezone\" : \"{{user `unattend_timezone`}}\"\n, \"admin_password\" : \"{{user `windows_admin_password`}}\"\n}", + "content": "{\n \"unattend_timezone\" : \"{{user `unattend_timezone`}}\"\n, \"admin_password\" : \"{{user `windows_admin_password`}}\"\n, \"ipv4_address_cidr\" : \"{{user `ipv4_address_cidr`}}\"\n, \"gateway4\" : \"{{user `gateway4`}}\"\n, \"dns_servers\" : \"{{user `dns_servers`}}\"\n}", "target": "./packer_cache/unattend.json", "type": "file" }, @@ -248,10 +248,13 @@ "containerd_version": null, "disable_hypervisor": null, "disk_size": "81920", + "dns_servers": null, "firmware": "bios", + "gateway4": null, "http_port_max": "", "http_port_min": "", "ib_version": "{{env `IB_VERSION`}}", + "ipv4_address_cidr": null, "kubernetes_base_url": "https://kubernetesreleases.blob.core.windows.net/kubernetes/{{user `kubernetes_semver`}}/binaries/node/windows/{{user `kubernetes_goarch`}}", "kubernetes_http_package_url": "", "kubernetes_typed_version": "kube-{{user `kubernetes_semver`}}",