From fc6f9d449a80fd8e86ecafb124a36a4b40568b1c Mon Sep 17 00:00:00 2001 From: Jonathan Senkerik Date: Thu, 5 Dec 2019 21:03:47 -0500 Subject: [PATCH 1/2] v1.6.1 Fix some refesh bugs, allow http(s) ovf sources. --- README.md | 83 ++----------------- esxi/guest-create.go | 82 ++++++++++-------- esxi/guest-read.go | 27 +++--- esxi/guest_functions.go | 21 ++++- esxi/resource_guest.go | 58 +++++++++---- .../05 CloudInit and Templates/main.tf | 4 +- examples/05 CloudInit and Templates/main.tf | 5 +- examples/06 OVF Properties/README.md | 75 +++++++++++++++++ examples/06 OVF Properties/main.tf | 52 ++++++++++++ examples/06 OVF Properties/outputs.tf | 8 ++ examples/06 OVF Properties/userdata.tpl | 16 ++++ examples/06 OVF Properties/variables.tf | 38 +++++++++ examples/06 OVF Properties/versions.tf | 4 + go.mod | 2 +- version | 2 +- 15 files changed, 326 insertions(+), 151 deletions(-) create mode 100644 examples/06 OVF Properties/README.md create mode 100644 examples/06 OVF Properties/main.tf create mode 100644 examples/06 OVF Properties/outputs.tf create mode 100644 examples/06 OVF Properties/userdata.tpl create mode 100644 examples/06 OVF Properties/variables.tf create mode 100644 examples/06 OVF Properties/versions.tf diff --git a/README.md b/README.md index 03a0994..0b81fa8 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Terraform Provider Requirements ------------ - [Terraform](https://www.terraform.io/downloads.html) 0.10.1+ -- [Go](https://golang.org/doc/install) 1.11.5 (to build the provider plugin) +- [Go](https://golang.org/doc/install) 1.11+ (to build the provider plugin) - [ovftool](https://www.vmware.com/support/developer/ovf/) from VMware. NOTE: ovftool installer for windows doesn't put ovftool.exe in your path. You will need to manually set your path. - You MUST enable ssh access on your ESXi hypervisor. * Google 'How to enable ssh access on esxi' @@ -182,8 +182,8 @@ Configuration reference * virtual_disk_id - Required - virtual_disk.id from esxi_virtual_disk resource. * slot - Required - SCSI_Ctrl:SCSI_id. Range '0:1' to '3:15'. SCSI_id 7 is not allowed. * power - Optional - on, off. - * guest_startup_timeout - Optional - The amount of guest uptime, in seconds, to wait for an available IP address on this virtual machine. - * guest_shutdown_timeout - Optional - The amount of time, in seconds, to wait for a graceful shutdown before doing a forced power off. + * guest_startup_timeout - Optional - The amount of guest uptime, in seconds, to wait for an available IP address on this virtual machine. Default 120s. + * guest_shutdown_timeout - Optional - The amount of time, in seconds, to wait for a graceful shutdown before doing a forced power off. Default 20s. * notes - Optional - The Guest notes (annotation). * guestinfo - Optional - The Guestinfo root * metadata - Optional - A JSON string containing the cloud-init metadata. @@ -192,77 +192,10 @@ Configuration reference * userdata.encoding - Optional - The encoding type for guestinfo.userdata. (base64 or gzip+base64) * vendordata - Optional - A YAML document containing the cloud-init vendor data. * vendordata.encoding - Optional - The encoding type for guestinfo.vendordata (base64 or gzip+base64) - * ovf_property - Optional - List of ovf properties to override in ova - * name - Required - Name of the property + * ovf_properties - Optional - List of ovf properties to override in ovf/ova sources. + * key - Required - Key of the property * value - Required - Value of the property - -Notes on ova files deploying ----------------------------- - -Having downloaded ova template, you can explore supported properties by using command - -``` -ovftool --hideEula image.ova -``` - -Example output - -``` -Properties: - Key: instance-id - Label: A Unique Instance ID for this instance - Type: string - Description: Specifies the instance id. This is required and used to - determine if the machine should take "first boot" actions - Value: id-ovf - - Key: hostname - Type: string - Description: Specifies the hostname for the appliance - Value: ubuntuguest - - Key: user-data - Label: Encoded user-data - Type: string - Description: In order to fit into a xml attribute, this value is base64 - encoded . It will be decoded, and then processed normally as - user-data. - - Key: password - Label: Default User's password - Type: string - Description: If set, the default user's password will be set to this value to - allow password based login. The password will be good for only - a single login. If set to the string 'RANDOM' then a random - password will be generated, and written to the console. - Value: q - -``` - -In particular, you might discover that OVA image accepts cloud-init data via specific parameter called `user-data`, -like in ubuntu cloud image https://cloud-images.ubuntu.com/bionic/current/ - -Thus for such images, you will need to inject initialization template for cloud-init via ovf_property, -as guest_info might not work for your scenario. Note, that in this case cloud-init template can override -some other ovf_properties, like `password` in example below. - -``` - ovf_property { - name = "password" - value = "Passw0rd1" - } - - ovf_property { - name = "hostname" - value = "HelloWorld" - } - - ovf_property { - name = "user-data" - value = base64encode(data.template_file.userdata_default.rendered) - } - ``` - + * ovf_properties_timer - Optional - Length of time to wait for ovf_properties to process. Default 90s. Known issues with vmware_esxi @@ -277,7 +210,9 @@ Known issues with vmware_esxi Version History --------------- -* 1.5.4.Fix bare-metal build when using additional virtual disks. +* 1.6.1 Fix some minor refesh bugs, allow http(s) ovf sources. +* 1.6.0 Add support for ovf_properties for OVF/OVA sources. +* 1.5.4 Fix bare-metal build when using additional virtual disks. * 1.5.3 Fix introduced bug when creating a bare-metal guest. * 1.5.2 Handle large userdata using scp. Connectivity test will retry only 3 times to help prevent account lockout. * 1.5.1 Windows Fix for special characters in esxi password. diff --git a/esxi/guest-create.go b/esxi/guest-create.go index 490293b..4150f9f 100644 --- a/esxi/guest-create.go +++ b/esxi/guest-create.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "log" + "net/http" "net/url" "os" "os/exec" @@ -16,7 +17,7 @@ import ( func guestCREATE(c *Config, guest_name string, disk_store string, src_path string, resource_pool_name string, strmemsize string, strnumvcpus string, strvirthwver string, guestos string, boot_disk_type string, boot_disk_size string, virtual_networks [10][3]string, - virtual_disks [60][2]string, guest_shutdown_timeout int, notes string, + virtual_disks [60][2]string, guest_shutdown_timeout int, ovf_properties_timer int, notes string, guestinfo map[string]interface{}, ovf_properties map[string]string) (string, error) { esxiSSHinfo := SshConnectionStruct{c.esxiHostName, c.esxiHostPort, c.esxiUserName, c.esxiPassword} @@ -27,7 +28,9 @@ func guestCREATE(c *Config, guest_name string, disk_store string, var osShellCmd, osShellCmdOpt string var out bytes.Buffer var err error + var is_ovf_properties bool err = nil + is_ovf_properties = false memsize, _ = strconv.Atoi(strmemsize) numvcpus, _ = strconv.Atoi(strnumvcpus) @@ -81,7 +84,7 @@ func guestCREATE(c *Config, guest_name string, disk_store string, remote_cmd = fmt.Sprintf("mkdir %s", fullPATH) stdout, err = runRemoteSshCommand(esxiSSHinfo, remote_cmd, "create guest path") if err != nil { - log.Printf("Failed to create guest path. fullPATH:%s\n", fullPATH) + log.Printf("[guestCREATE] Failed to create guest path. fullPATH:%s\n", fullPATH) return "", fmt.Errorf("Failed to create guest path. fullPATH:%s\n", fullPATH) } } @@ -165,20 +168,20 @@ func guestCREATE(c *Config, guest_name string, disk_store string, if err != nil { remote_cmd = fmt.Sprintf("rm -fr %s", fullPATH) stdout, _ = runRemoteSshCommand(esxiSSHinfo, remote_cmd, "cleanup guest path because of failed events") - log.Printf("Failed to vmkfstools (make boot disk):%s\n", err.Error()) + log.Printf("[guestCREATE] Failed to vmkfstools (make boot disk):%s\n", err.Error()) return "", fmt.Errorf("Failed to vmkfstools (make boot disk):%s\n", err.Error()) } poolID, err := getPoolID(c, resource_pool_name) log.Println("[guestCREATE] DEBUG: " + poolID) if err != nil { - log.Printf("Failed to use Resource Pool ID:%s\n", poolID) + log.Printf("[guestCREATE] Failed to use Resource Pool ID:%s\n", poolID) return "", fmt.Errorf("Failed to use Resource Pool ID:%s\n", poolID) } remote_cmd = fmt.Sprintf("vim-cmd solo/registervm %s %s %s", dst_vmx_file, guest_name, poolID) _, err = runRemoteSshCommand(esxiSSHinfo, remote_cmd, "solo/registervm") if err != nil { - log.Printf("Failed to register guest:%s\n", err.Error()) + log.Printf("[guestCREATE] Failed to register guest:%s\n", err.Error()) remote_cmd = fmt.Sprintf("rm -fr %s", fullPATH) stdout, _ = runRemoteSshCommand(esxiSSHinfo, remote_cmd, "cleanup guest path because of failed events") return "", fmt.Errorf("Failed to register guest:%s\n", err.Error()) @@ -188,8 +191,22 @@ func guestCREATE(c *Config, guest_name string, disk_store string, // Build VM by ovftool // Check if source file exist. - if !strings.HasPrefix(src_path, "vi://") { + if strings.HasPrefix(src_path, "http://") || strings.HasPrefix(src_path, "https://") { + log.Printf("[guestCREATE] Source is URL.\n") + resp, err := http.Get(src_path) + defer resp.Body.Close() + if (err != nil) || (resp.StatusCode != 200) { + log.Printf("[guestCREATE] URL not accessible: %s\n", src_path) + log.Printf("[guestCREATE] URL StatusCode: %s\n", resp.StatusCode) + log.Printf("[guestCREATE] URL Error: %s\n", err.Error()) + return "", fmt.Errorf("URL not accessible: %s\n%s", src_path, err.Error()) + } + } else if strings.HasPrefix(src_path, "vi://") { + log.Printf("[guestCREATE] Source is Guest VM (vi).\n") + } else { + log.Printf("[guestCREATE] Source is local.\n") if _, err := os.Stat(src_path); os.IsNotExist(err) { + log.Printf("[guestCREATE] File not found, Error: %s\n", err.Error()) return "", fmt.Errorf("File not found: %s\n", src_path) } } @@ -206,28 +223,22 @@ func guestCREATE(c *Config, guest_name string, disk_store string, net_param = " --network='" + virtual_networks[0][0] + "'" } - extra_params := "--X:injectOvfEnv --allowExtraConfig " - if (strings.HasSuffix(src_path, ".ova") || strings.HasSuffix(src_path, ".ovf")) { + extra_params := "" + if (len(ovf_properties) > 0) && (strings.HasSuffix(src_path, ".ova") || strings.HasSuffix(src_path, ".ovf")) { + is_ovf_properties = true // in order to process any OVF params, guest should be immediately powered on // This is because the ESXi host doesn't have a cache to store the OVF parameters, like the vCenter Server does. - // Therefore, you MUST use the ‘--X:injectOvfEnv’ debug option with the ‘--poweron’ option - if len(ovf_properties) > 0 { - extra_params = extra_params + "--powerOn " - } + // Therefore, you MUST use the ‘--X:injectOvfEnv’ option with the ‘--poweron’ option + extra_params = "--X:injectOvfEnv --allowExtraConfig --powerOn " - if (strings.HasSuffix(src_path, ".ova")) { - extra_params = extra_params + "--sourceType=OVA " - } else { - extra_params = extra_params + "--sourceType=OVF " + for ovf_prop_key, ovf_prop_value := range ovf_properties { + extra_params = fmt.Sprintf("%s --prop:%s='%s' ", extra_params, ovf_prop_key, ovf_prop_value) } - - for ovf_prop_name, ovf_prop_value := range ovf_properties { - extra_params = fmt.Sprintf("%s --prop:%s=\"%s\"", extra_params, ovf_prop_name, ovf_prop_value) - } + log.Println("[guestCREATE] ovf_properties extra_params: " + extra_params) } ovf_cmd := fmt.Sprintf("ovftool --acceptAllEulas --noSSLVerify --X:useMacNaming=false %s "+ - "-dm=%s --name='%s' --overwrite -ds='%s' %s '%s' '%s'",extra_params, boot_disk_type, guest_name, disk_store, net_param, src_path, dst_path) + "-dm=%s --name='%s' --overwrite -ds='%s' %s '%s' '%s'", extra_params, boot_disk_type, guest_name, disk_store, net_param, src_path, dst_path) if runtime.GOOS == "windows" { osShellCmd = "cmd.exe" @@ -279,7 +290,7 @@ func guestCREATE(c *Config, guest_name string, disk_store string, log.Printf("[guestCREATE] ovftool output: %q\n", out.String()) if err != nil { - log.Printf("Failed, There was an ovftool Error: %s\n%s\n", out.String(), err.Error()) + log.Printf("[guestCREATE] Failed, There was an ovftool Error: %s\n%s\n", out.String(), err.Error()) return "", fmt.Errorf("There was an ovftool Error: %s\n%s\n", out.String(), err.Error()) } } @@ -291,20 +302,25 @@ func guestCREATE(c *Config, guest_name string, disk_store string, } // - // In case of OVA VM, we need to spin it up in "running" state to pass params, thus - // we need to shutdown it before proceeding with disk resize - // Possible, that we need to wait for initial cloud-init process to finish, i.e. shutdown gracefully. + // ovf_properties require ovftool to power on the VM to inject the properties. + // Unfortunatly, there is no way to know when cloud-init is finished?!?!? Just need + // to wait for ovf_properties_timer seconds, then shutdown/power-off to continue... // - currentpowerstate := guestPowerGetState(c, vmid) - fmt.Println(fmt.Sprintf(">>> Current VM PowerState is %s", currentpowerstate)) - if currentpowerstate == "on" { - // allow cloud-init process, if any to initially provision instance - // before doing resizing of the boot disk itself - duration := time.Duration(90)*time.Second + if is_ovf_properties == true { + currentpowerstate := guestPowerGetState(c, vmid) + log.Printf("[guestCREATE] Current VM PowerState: %s\n", currentpowerstate) + if currentpowerstate != "on" { + return vmid, fmt.Errorf("[guestCREATE] Failed to poweron after ovf_properties injection.\n") + } + // allow cloud-init to process. + duration := time.Duration(ovf_properties_timer) * time.Second + + log.Printf("[guestCREATE] Waiting for ovf_properties_timer: %s\n", duration) + time.Sleep(duration) _, err = guestPowerOff(c, vmid, guest_shutdown_timeout) if err != nil { - return vmid, fmt.Errorf("Failed to gracefully shutdown machine before resizing boot disk.\n") + return vmid, fmt.Errorf("[guestCREATE] Failed to shutdown after ovf_properties injection.\n") } } @@ -315,7 +331,7 @@ func guestCREATE(c *Config, guest_name string, disk_store string, err = growVirtualDisk(c, boot_disk_vmdkPATH, boot_disk_size) if err != nil { - return vmid, fmt.Errorf("Failed to grow boot disk.\n") + return vmid, fmt.Errorf("[guestCREATE] Failed to grow boot disk.\n") } // diff --git a/esxi/guest-read.go b/esxi/guest-read.go index a1f6b5c..9b69b6b 100644 --- a/esxi/guest-read.go +++ b/esxi/guest-read.go @@ -38,17 +38,8 @@ func resourceGUESTRead(d *schema.ResourceData, m interface{}) error { d.Set("ip_address", ip_address) d.Set("power", power) d.Set("notes", notes) - d.Set("guestinfo", guestinfo) - - if d.Get("guest_startup_timeout").(int) > 1 { - d.Set("guest_startup_timeout", d.Get("guest_startup_timeout").(int)) - } else { - d.Set("guest_startup_timeout", 60) - } - if d.Get("guest_shutdown_timeout").(int) > 0 { - d.Set("guest_shutdown_timeout", d.Get("guest_shutdown_timeout").(int)) - } else { - d.Set("guest_shutdown_timeout", 20) + if len(guestinfo) != 0 { + d.Set("guestinfo", guestinfo) } // Do network interfaces @@ -234,16 +225,18 @@ func guestREAD(c *Config, vmid string, guest_startup_timeout int) (string, strin isGeneratedMAC[index] = true } - case "generatedAddress": - if isGeneratedMAC[index] == true { - virtual_networks[index][1] = results[3] - log.Printf("[guestREAD] %s : %s\n", results[0], results[3]) - } + // Done't save generatedAddress... It should not be saved because it + // should be considered dynamic & is breaks the update MAC address code. + //case "generatedAddress": + // if isGeneratedMAC[index] == true { + // virtual_networks[index][1] = results[3] + // log.Printf("[guestREAD] %s : %s\n", results[0], results[3]) + // } case "address": if isGeneratedMAC[index] == false { virtual_networks[index][1] = results[3] - log.Printf("[resourceGUESTRead] %s : %s\n", results[0], results[3]) + log.Printf("[guestREAD] %s : %s\n", results[0], results[3]) } case "virtualDev": diff --git a/esxi/guest_functions.go b/esxi/guest_functions.go index 5a2082e..3727826 100644 --- a/esxi/guest_functions.go +++ b/esxi/guest_functions.go @@ -251,7 +251,7 @@ func updateVmx_contents(c *Config, vmid string, iscreate bool, memsize int, numv if virtual_networks[i][0] == "" && strings.Contains(vmx_contents, "ethernet"+strconv.Itoa(i)) == true { // This is Modify (Delete existing network configuration) - log.Printf("[updateVmx_contents] ethernet%d Delete existing.\n", i) + log.Printf("[updateVmx_contents] Modify ethernet%d - Delete existing.\n", i) regexReplacement = fmt.Sprintf("") re := regexp.MustCompile(fmt.Sprintf("ethernet%d.*\n", i)) vmx_contents = re.ReplaceAllString(vmx_contents, regexReplacement) @@ -259,7 +259,7 @@ func updateVmx_contents(c *Config, vmid string, iscreate bool, memsize int, numv if virtual_networks[i][0] != "" && strings.Contains(vmx_contents, "ethernet"+strconv.Itoa(i)) == true { // This is Modify - log.Printf("[updateVmx_contents] ethernet%d Modify existing.\n", i) + log.Printf("[updateVmx_contents] Modify ethernet%d - Modify existing.\n", i) // Modify Network Name re := regexp.MustCompile("ethernet" + strconv.Itoa(i) + ".networkName = \".*\"") @@ -271,7 +271,22 @@ func updateVmx_contents(c *Config, vmid string, iscreate bool, memsize int, numv regexReplacement = fmt.Sprintf("ethernet"+strconv.Itoa(i)+".virtualDev = \"%s\"", virtual_networks[i][2]) vmx_contents = re.ReplaceAllString(vmx_contents, regexReplacement) - // Modify MAC todo + // Modify MAC (dynamic to static only. static to dynamic is not implemented) + if virtual_networks[i][1] != "" { + log.Printf("[updateVmx_contents] ethernet%d Modify MAC: %s\n", i, virtual_networks[i][0]) + + re = regexp.MustCompile("ethernet" + strconv.Itoa(i) + ".[a-zA-Z]*ddress = \".*\"") + regexReplacement = fmt.Sprintf("ethernet"+strconv.Itoa(i)+".address = \"%s\"", virtual_networks[i][1]) + vmx_contents = re.ReplaceAllString(vmx_contents, regexReplacement) + + re = regexp.MustCompile("ethernet" + strconv.Itoa(i) + ".addressType = \".*\"") + regexReplacement = fmt.Sprintf("ethernet" + strconv.Itoa(i) + ".addressType = \"static\"") + vmx_contents = re.ReplaceAllString(vmx_contents, regexReplacement) + + re = regexp.MustCompile("ethernet" + strconv.Itoa(i) + ".generatedAddressOffset = \".*\"") + regexReplacement = fmt.Sprintf("") + vmx_contents = re.ReplaceAllString(vmx_contents, regexReplacement) + } } if virtual_networks[i][0] != "" && strings.Contains(vmx_contents, "ethernet"+strconv.Itoa(i)) == false { diff --git a/esxi/resource_guest.go b/esxi/resource_guest.go index e29c80d..e1eb6e3 100644 --- a/esxi/resource_guest.go +++ b/esxi/resource_guest.go @@ -144,7 +144,7 @@ func resourceGUEST() *schema.Resource { Optional: true, Computed: true, Description: "The amount of guest uptime, in seconds, to wait for an available IP address on this virtual machine.", - ValidateFunc: validation.IntBetween(1, 600), + ValidateFunc: validation.IntBetween(0, 600), }, "guest_shutdown_timeout": { Type: schema.TypeInt, @@ -174,25 +174,33 @@ func resourceGUEST() *schema.Resource { }, }, }, - "ovf_property": &schema.Schema{ + "ovf_properties": &schema.Schema{ Type: schema.TypeList, Optional: true, Computed: false, + ForceNew: true, Default: nil, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "name": &schema.Schema{ - Type: schema.TypeString, - Required: true, + "key": &schema.Schema{ + Type: schema.TypeString, + Required: true, }, "value": &schema.Schema{ - Type: schema.TypeString, - Required: true, - Computed: false, + Type: schema.TypeString, + Required: true, + Computed: false, }, }, }, }, + "ovf_properties_timer": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + Description: "The amount of time, in seconds, to wait for the guest to boot and run ovf_properties.", + ValidateFunc: validation.IntBetween(0, 6000), + }, "notes": &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -221,7 +229,7 @@ func resourceGUESTCreate(d *schema.ResourceData, m interface{}) error { var virtual_networks [10][3]string var virtual_disks [60][2]string var src_path string - var tmpint, i, virtualDiskCount, ovfPropsCount int + var tmpint, i, virtualDiskCount, ovfPropsCount, guest_shutdown_timeout, ovf_properties_timer int var ovf_properties map[string]string clone_from_vm := d.Get("clone_from_vm").(string) @@ -237,7 +245,24 @@ func resourceGUESTCreate(d *schema.ResourceData, m interface{}) error { guestos := d.Get("guestos").(string) notes := d.Get("notes").(string) power := d.Get("power").(string) - guest_shutdown_timeout := d.Get("guest_shutdown_timeout").(int) + + if d.Get("guest_startup_timeout").(int) >= 0 { + d.Set("guest_startup_timeout", d.Get("guest_startup_timeout").(int)) + } else { + d.Set("guest_startup_timeout", 120) + } + if d.Get("guest_shutdown_timeout").(int) >= 0 { + d.Set("guest_shutdown_timeout", d.Get("guest_shutdown_timeout").(int)) + guest_shutdown_timeout = d.Get("guest_shutdown_timeout").(int) + } else { + d.Set("guest_shutdown_timeout", 20) + } + if d.Get("ovf_properties_timer").(int) >= 0 { + d.Set("ovf_properties_timer", d.Get("ovf_properties_timer").(int)) + ovf_properties_timer = d.Get("ovf_properties_timer").(int) + } else { + ovf_properties_timer = 90 + } guestinfo, ok := d.Get("guestinfo").(map[string]interface{}) if !ok { @@ -343,7 +368,7 @@ func resourceGUESTCreate(d *schema.ResourceData, m interface{}) error { } // Parse ovf properties, if any - ovfPropsCount, ok = d.Get("ovf_property.#").(int) + ovfPropsCount, ok = d.Get("ovf_properties.#").(int) if !ok { ovfPropsCount = 0 } else { @@ -351,20 +376,19 @@ func resourceGUESTCreate(d *schema.ResourceData, m interface{}) error { } for i = 0; i < ovfPropsCount; i++ { - prefix := fmt.Sprintf("ovf_property.%d.", i) + prefix := fmt.Sprintf("ovf_properties.%d.", i) - if name, ok := d.Get(prefix + "name").(string); ok && name != "" { + if key, ok := d.Get(prefix + "key").(string); ok && key != "" { if value, ok := d.Get(prefix + "value").(string); ok && value != "" { - ovf_properties[name] = value + ovf_properties[key] = value } - } + } } - vmid, err := guestCREATE(c, guest_name, disk_store, src_path, resource_pool_name, memsize, numvcpus, virthwver, guestos, boot_disk_type, boot_disk_size, virtual_networks, - virtual_disks, guest_shutdown_timeout, notes, guestinfo, ovf_properties) + virtual_disks, guest_shutdown_timeout, ovf_properties_timer, notes, guestinfo, ovf_properties) if err != nil { tmpint, _ = strconv.Atoi(vmid) if tmpint > 0 { diff --git a/examples-legacy/05 CloudInit and Templates/main.tf b/examples-legacy/05 CloudInit and Templates/main.tf index 7866065..e8e89e4 100644 --- a/examples-legacy/05 CloudInit and Templates/main.tf +++ b/examples-legacy/05 CloudInit and Templates/main.tf @@ -14,11 +14,11 @@ provider "esxi" { ######################################### # cloud-init for vmware! # You must install it on your source VM before cloning it! -# See https://github.com/akutz/cloud-init-vmware-guestinfo for more details. +# See https://github.com/vmware/cloud-init-vmware-guestinfo for more details. # and https://cloudinit.readthedocs.io/en/latest/topics/examples.html# # # -# yum install https://github.com/akutz/cloud-init-vmware-guestinfo/releases/download/v1.1.0/cloud-init-vmware-guestinfo-1.1.0-1.el7.noarch.rpm +# yum install https://github.com/vmware/cloud-init-vmware-guestinfo/releases/download/v1.1.0/cloud-init-vmware-guestinfo-1.1.0-1.el7.noarch.rpm # cloud-init clean ######################################### diff --git a/examples/05 CloudInit and Templates/main.tf b/examples/05 CloudInit and Templates/main.tf index d5ed36e..52e7a6f 100644 --- a/examples/05 CloudInit and Templates/main.tf +++ b/examples/05 CloudInit and Templates/main.tf @@ -14,11 +14,11 @@ provider "esxi" { ######################################### # cloud-init for vmware! # You must install it on your source VM before cloning it! -# See https://github.com/akutz/cloud-init-vmware-guestinfo for more details. +# See https://github.com/vmware/cloud-init-vmware-guestinfo for more details. # and https://cloudinit.readthedocs.io/en/latest/topics/examples.html# # # -# yum install https://github.com/akutz/cloud-init-vmware-guestinfo/releases/download/v1.1.0/cloud-init-vmware-guestinfo-1.1.0-1.el7.noarch.rpm +# yum install https://github.com/vmware/cloud-init-vmware-guestinfo/releases/download/v1.1.0/cloud-init-vmware-guestinfo-1.1.0-1.el7.noarch.rpm # cloud-init clean ######################################### @@ -52,4 +52,3 @@ resource "esxi_guest" "Default" { "userdata" = base64gzip(data.template_file.Default.rendered) } } - diff --git a/examples/06 OVF Properties/README.md b/examples/06 OVF Properties/README.md new file mode 100644 index 0000000..3111220 --- /dev/null +++ b/examples/06 OVF Properties/README.md @@ -0,0 +1,75 @@ +# Terraform esxi Provider (06 OVF Properties) +--- + +## Notes on ovf_properties. +* There are a few caveats using this method of configuring your guest. + * This method of injecting properties into the VM works with ovf/ova sources only. The guestinfo method ("05 CloudInit and Templates" example) can be used with any source type (vmx, ovf, ova, clone_from_vm), but requires "Cloud-Init for VMware" to be pre-installed on the source. + + * 'terraform apply' takes longer to deploy when using ovf_properties because the guest will be booted twice. The default length of time to allow ovf_properties to run is 90 seconds. If your configuration requires more time, set ovf_properties_timer to a higher value. + * You cannot configure additional virtual_disks in userdata using the ovf_properties method. Userdata in ovf_properties runs before the additional virtual_disks are added. If this is a requirement, use the guestinfo (Cloud-Init for VMware) method. + + * This method only works on ovf/ova sources that have properties available to configure. Not all ovf/ova files will have properties or their Key naming convention may not be consistent between sources. + +## How to use ovf_properties. +* Some ovf/ova files have available properties. These key/value properties can be retrieved from the ovf/ova file. The default values of the guest can be over written using the ovf_properties configuration option in your terraform code. + +* To list the OVF properties in a specific ovf/ova file, use the following command. + +``` +ovftool --hideEula image.ova +``` + +Example output + +``` +Properties: + Key: instance-id + Label: A Unique Instance ID for this instance + Type: string + Description: Specifies the instance id. This is required and used to + determine if the machine should take "first boot" actions + Value: id-ovf + + Key: hostname + Type: string + Description: Specifies the hostname for the appliance + Value: ubuntuguest + + Key: user-data + Label: Encoded user-data + Type: string + Description: In order to fit into a xml attribute, this value is base64 + encoded . It will be decoded, and then processed normally as + user-data. + + Key: password + Label: Default User's password + Type: string + Description: If set, the default user's password will be set to this value to + allow password based login. The password will be good for only + a single login. If set to the string 'RANDOM' then a random + password will be generated, and written to the console. + Value: q + +``` + +* The `user-data` key allows you to inject user-data via ovf_properties. This works with images that have cloud-init included. + * For example: Ubuntu cloud-images. https://cloud-images.ubuntu.com/ + +* The following example will set the "Default User's Password" and hostname. It will also pass the rendered userdata.tpl file to the vm. +``` + ovf_properties { + key = "password" + value = "Passw0rd1" + } + + ovf_properties { + key = "hostname" + value = "vmtest06" + } + + ovf_properties { + key = "user-data" + value = base64encode(data.template_file.userdata_default.rendered) + } + ``` diff --git a/examples/06 OVF Properties/main.tf b/examples/06 OVF Properties/main.tf new file mode 100644 index 0000000..756d7e3 --- /dev/null +++ b/examples/06 OVF Properties/main.tf @@ -0,0 +1,52 @@ +provider "esxi" { + esxi_hostname = var.esxi_hostname + esxi_hostport = var.esxi_hostport + esxi_username = var.esxi_username + esxi_password = var.esxi_password +} + +# +# Template for initial configuration bash script +# template_file is a great way to pass variables to +# cloud-init +data "template_file" "userdata_default" { + template = file("userdata.tpl") + vars = { + HOSTNAME = var.vm_hostname + HELLO = "Hello EXSI World!" + } +} + +resource "esxi_guest" "vmtest" { + guest_name = var.vm_hostname + disk_store = var.disk_store + + network_interfaces { + virtual_network = var.virtual_network + } + + guestinfo = { + "userdata.encoding" = "gzip+base64" + "userdata" = base64gzip(data.template_file.userdata_default.rendered) + } + + # + # Specify an ovf file to use as a source. + # + ovf_source = var.ovf_file + + ovf_property { + key = "password" + value = "Passw0rd1" + } + + ovf_property { + key = "hostname" + value = "HelloWorld" + } + + ovf_property { + key = "user-data" + value = base64encode(data.template_file.userdata_default.rendered) + } +} diff --git a/examples/06 OVF Properties/outputs.tf b/examples/06 OVF Properties/outputs.tf new file mode 100644 index 0000000..cf7ff2c --- /dev/null +++ b/examples/06 OVF Properties/outputs.tf @@ -0,0 +1,8 @@ + +output "ip" { + value = esxi_guest.vmtest.ip_address +} + +output "cloudinit" { + value = data.template_file.userdata_default.rendered +} diff --git a/examples/06 OVF Properties/userdata.tpl b/examples/06 OVF Properties/userdata.tpl new file mode 100644 index 0000000..eb0e5a8 --- /dev/null +++ b/examples/06 OVF Properties/userdata.tpl @@ -0,0 +1,16 @@ +#cloud-config + +packages: + - ntp + - ntpdate + - curl + +# Override ntp with chrony configuration on Ubuntu +ntp: + enabled: true + ntp_client: chrony # Uses cloud-init default chrony configuration + +runcmd: + - date >/root/cloudinit.log + - echo ${HELLO} >>/root/cloudinit.log + - echo "Done cloud-init" >>/root/cloudinit.log diff --git a/examples/06 OVF Properties/variables.tf b/examples/06 OVF Properties/variables.tf new file mode 100644 index 0000000..dc7579b --- /dev/null +++ b/examples/06 OVF Properties/variables.tf @@ -0,0 +1,38 @@ +# +# See https://www.terraform.io/intro/getting-started/variables.html for more details. +# + +# Change these defaults to fit your needs! + +variable "esxi_hostname" { + default = "esxi" +} + +variable "esxi_hostport" { + default = "22" +} + +variable "esxi_username" { + default = "root" +} + +variable "esxi_password" { + # Unspecified will prompt +} + +variable "virtual_network" { + default = "VM Network" +} + +variable "disk_store" { + default = "DiskStore01" +} + +# Example downloaded from https://cloud-images.ubuntu.com/ +variable "ovf_file" { + default = "ubuntu-19.04-server-cloudimg-amd64.ova" +} + +variable "vm_hostname" { + default = "vmtest06" +} diff --git a/examples/06 OVF Properties/versions.tf b/examples/06 OVF Properties/versions.tf new file mode 100644 index 0000000..ac97c6a --- /dev/null +++ b/examples/06 OVF Properties/versions.tf @@ -0,0 +1,4 @@ + +terraform { + required_version = ">= 0.12" +} diff --git a/go.mod b/go.mod index c838401..bbfd73c 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module terraform-provider-esxi require ( github.com/hashicorp/terraform v0.12.2 - github.com/josenk/terraform-provider-esxi v1.5.4 + github.com/josenk/terraform-provider-esxi v1.6.1 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/tmc/scp v0.0.0-20170824174625-f7b48647feef golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 diff --git a/version b/version index f074f24..0236045 100644 --- a/version +++ b/version @@ -1 +1 @@ -v1.5.4 +v1.6.1 From 9d77e6824dc9eacb15e4c891b73aa6304264d0e1 Mon Sep 17 00:00:00 2001 From: Jonathan Senkerik Date: Fri, 6 Dec 2019 22:18:23 -0500 Subject: [PATCH 2/2] docs --- README.md | 10 ++ .../06 CloudInit and Ova Template/main.tf | 98 ------------------- .../06 CloudInit and Ova Template/outputs.tf | 8 -- .../userdata.tpl | 45 --------- .../variables.tf | 43 -------- .../06 CloudInit and Ova Template/versions.tf | 4 - examples/06 OVF Properties/main.tf | 6 +- 7 files changed, 13 insertions(+), 201 deletions(-) delete mode 100644 examples/06 CloudInit and Ova Template/main.tf delete mode 100644 examples/06 CloudInit and Ova Template/outputs.tf delete mode 100644 examples/06 CloudInit and Ova Template/userdata.tpl delete mode 100644 examples/06 CloudInit and Ova Template/variables.tf delete mode 100644 examples/06 CloudInit and Ova Template/versions.tf diff --git a/README.md b/README.md index 0b81fa8..2a00c7f 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,8 @@ Terraform-provider-esxi plugin >https://configmax.vmware.com/guest + + What's New: ----------- * Terraform can import existing Guest VMs, Virtual Disks & Resource pools by name. See wiki page for more info. @@ -71,6 +73,13 @@ Features and Compatibility * Terraform will Create, Destroy, Update & Read Guest VMs. * Terraform will Create, Destroy, Update & Read Extra Storage for Guests. +This is a provider! NOT a provisioner. +--------------------------------------- +* This plugin does not configure your guest VM, it creates it. +* To configure your guest VM after it's built, you need to use a provisioner. + * Refer to Hashicorp list of provisioners: https://www.terraform.io/docs/provisioners/index.html +* To help you get started, there is are examples in a separate repo I created. You can create a Pull Request if you would like to contribute. + * https://github.com/josenk/terraform-provider-esxi-wiki Vagrant vs Terraform. --------------------- @@ -208,6 +217,7 @@ Known issues with vmware_esxi * Using an incorrect password could lockout your account using default esxi pam settings. + Version History --------------- * 1.6.1 Fix some minor refesh bugs, allow http(s) ovf sources. diff --git a/examples/06 CloudInit and Ova Template/main.tf b/examples/06 CloudInit and Ova Template/main.tf deleted file mode 100644 index 8471ea0..0000000 --- a/examples/06 CloudInit and Ova Template/main.tf +++ /dev/null @@ -1,98 +0,0 @@ -provider "esxi" { - esxi_hostname = var.esxi_hostname - esxi_hostport = var.esxi_hostport - esxi_username = var.esxi_username - esxi_password = var.esxi_password -} - -# -# Template for initial configuration bash script -# template_file is a great way to pass variables to -# cloud-init -data "template_file" "userdata_default" { - template = file("userdata.tpl") - vars = { - HOSTNAME = var.vm_hostname - HELLO = "Hello EXSI World!" - } -} - -resource "esxi_guest" "vmtest" { - # guest_name - Required - The Guest name. - guest_name = "vmtest" - # guestos # Optional - Default will be taken from cloned source - notes = "ProjectX" # Optional - The Guest notes (annotation). - disk_store = var.disk_store # Required - esxi Disk Store where guest vm will be created - boot_disk_type = "thin" # Optional - Guest boot disk type. Default 'thin'. Available thin, zeroedthick, eagerzeroedthick - boot_disk_size = "30" # Optional - Specify boot disk size or grow cloned vm to this size. - memsize = "4096" # Optional - Memory size in MB. (ie, 1024 == 1GB). See esxi documentation for limits. - Default 512 or default taken from cloned source - numvcpus = "1" # Optional - Number of virtual cpus. See esxi documentation for limits. - Default 1 or default taken from cloned source. - virthwver = "8" # Optional - esxi guest virtual HW version. See esxi documentation for compatible values. - Default 8 or taken from cloned source. - power = "on" # Optional - on, off. - guest_startup_timeout = "90" - - network_interfaces { - virtual_network = var.virtual_network - } - -// guestinfo = { -//// "coreos.config.data.encoding" = "base64" -//// "coreos.config.data" = base64encode(data.ignition_config.coreos.rendered) -// "userdata.encoding" = "gzip+base64" -// "userdata" = base64gzip(data.template_file.userdata_default.rendered) -// } - - guestinfo = { - "userdata.encoding" = "gzip+base64" - "userdata" = base64gzip(data.template_file.userdata_default.rendered) - } - - # - # Specify an existing guest to clone, an ovf source, or neither to build a bare-metal guest vm. - # - #clone_from_vm = var.vm_clone_from # Source vm to clone. Mutually exclusive with ovf_source option. - ovf_source = var.vm_ovf_local_path # ovf files to use as a source. Mutually exclusive with clone_from_vm option. - - ovf_property { - name = "password" - value = "Passw0rd1" - } - - ovf_property { - name = "hostname" - value = "HelloWorld" - } - - ovf_property { - name = "user-data" - value = base64encode(data.template_file.userdata_default.rendered) - } - - - #Array of upto 10 network interfaces. - #virtual_network - Required for each Guest NIC - This is the esxi virtual network name configured on esxi host. - #mac_address - Optional - If not set, mac_address will be generated by esxi. - #nic_type - Optional - See esxi documentation for compatibility list. - Default "e1000" or taken from cloned source. - - # Other optionals - - # resource_pool_name - Optional - Any existing or terraform managed resource pool name. - Default "/" - # virtual_disks - Optional - Array of additional storage to be added to the guest. - # virtual_disk_id - Required - virtual_disk.id from esxi_virtual_disk resource. - # slot - Required - SCSI_Ctrl:SCSI_id. Range '0:1' to '3:15'. SCSI_id 7 is not allowed. - # guest_startup_timeout - Optional - The amount of guest uptime, in seconds, to wait for an available IP address on this virtual machine. - # guest_shutdown_timeout - Optional - The amount of time, in seconds, to wait for a graceful shutdown before doing a forced power off. - - # guestinfo - Optional - The Guestinfo root - # metadata - Optional - A JSON string containing the cloud-init metadata. - # metadata.encoding - Optional - The encoding type for guestinfo.metadata. (base64 or gzip+base64) - # userdata - Optional - A YAML document containing the cloud-init user data. - # userdata.encoding - Optional - The encoding type for guestinfo.userdata. (base64 or gzip+base64) - # vendordata - Optional - A YAML document containing the cloud-init vendor data. - # vendordata.encoding - Optional - The encoding type for guestinfo.vendordata (base64 or gzip+base64) - - # /Other optionals - - # OUTPUTS - # ip_address - Computed - The IP address reported by VMware tools. -} diff --git a/examples/06 CloudInit and Ova Template/outputs.tf b/examples/06 CloudInit and Ova Template/outputs.tf deleted file mode 100644 index cf7ff2c..0000000 --- a/examples/06 CloudInit and Ova Template/outputs.tf +++ /dev/null @@ -1,8 +0,0 @@ - -output "ip" { - value = esxi_guest.vmtest.ip_address -} - -output "cloudinit" { - value = data.template_file.userdata_default.rendered -} diff --git a/examples/06 CloudInit and Ova Template/userdata.tpl b/examples/06 CloudInit and Ova Template/userdata.tpl deleted file mode 100644 index b6e23b9..0000000 --- a/examples/06 CloudInit and Ova Template/userdata.tpl +++ /dev/null @@ -1,45 +0,0 @@ -#cloud-config - -users: - - default - - name: slavko - passwd: $1$slavko$3qoKYqsEr9ZU9xhAiTTuB. - ssh_pwauth: True - chpasswd: { expire: False } - sudo: ALL=(ALL) NOPASSWD:ALL - groups: users - ssh_authorized_keys: - - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC07jki/9uEq9Pn+hry3lmGjMeJORdETVrpFL53X0aZLJp8KvWcqFf+P/ZdScHnVqr0VEt+axtyivSf0TrfB/i97n9lW2ZA0RY9UnURJqPpknfvidgosDzqGTFlOfdvE/tw7QzK0G8AmyxaQ0pf4ueD0brk4k0IVWX7oMdT/rJMb05owDaj3E6LK5smeGe86i9R9oFmBDxKTD9CSTzG8T70MsaNR4/brBtDFpDyVFzScnNB9xN8xXnalFiJBtkqyGYXqshGPxWMzTAXv6Zmjxe/hDUfBT0wgp6yscj0BCippsFyZ+LhK/ChamtZAEceWveH06nTc/+Kh3c59RMo0MvP slavko@rocketracoon -packages: - - ntp - - ntpdate - - curl - -# Override ntp with chrony configuration on Ubuntu -ntp: - enabled: true - ntp_client: chrony # Uses cloud-init default chrony configuration - - -# Add yum repository configuration to the system -#yum_repos: -# # The name of the repository -# epel-testing: -# # Any repository configuration options -# # See: man yum.conf -# # -# # This one is required! -# baseurl: http://download.fedoraproject.org/pub/epel/testing/5/$basearch -# enabled: false -# failovermethod: priority -# gpgcheck: true -# gpgkey: file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL -# name: Extra Packages for Enterprise Linux 5 - Testing - -runcmd: - - date > /tmp/cloudinit.log - - whoami >> /tmp/cloudinit.log - - sudo dhclient ens36 - - sudo hostnamectl set-hostname ${HOSTNAME} - - sudo echo ${HELLO} >> /tmp/cloudinit.log - - sudo echo "Done tf cloud-init" >>/tmp/cloudinit.log diff --git a/examples/06 CloudInit and Ova Template/variables.tf b/examples/06 CloudInit and Ova Template/variables.tf deleted file mode 100644 index e10d9a9..0000000 --- a/examples/06 CloudInit and Ova Template/variables.tf +++ /dev/null @@ -1,43 +0,0 @@ -# -# See https://www.terraform.io/intro/getting-started/variables.html for more details. -# - -# Change these defaults to fit your needs! - -variable "esxi_hostname" { - default = "esxi" -} - -variable "esxi_hostport" { - default = "22" -} - -variable "esxi_username" { - default = "root" -} - -variable "esxi_password" { - # Unspecified will prompt -} - -variable "virtual_network" { - default = "VM Network" -} - -variable "disk_store" { - default = "ESXI" -} - -# Application specific variables - -variable "vm_clone_from" { - default = "templateU18D" -} - -variable "vm_ovf_local_path" { - default = "/home/slavko/personal/ESXI/bionic/bionic.ova" -} - -variable "vm_hostname" { - default = "vmtest" -} diff --git a/examples/06 CloudInit and Ova Template/versions.tf b/examples/06 CloudInit and Ova Template/versions.tf deleted file mode 100644 index ac97c6a..0000000 --- a/examples/06 CloudInit and Ova Template/versions.tf +++ /dev/null @@ -1,4 +0,0 @@ - -terraform { - required_version = ">= 0.12" -} diff --git a/examples/06 OVF Properties/main.tf b/examples/06 OVF Properties/main.tf index 756d7e3..b88e138 100644 --- a/examples/06 OVF Properties/main.tf +++ b/examples/06 OVF Properties/main.tf @@ -35,17 +35,17 @@ resource "esxi_guest" "vmtest" { # ovf_source = var.ovf_file - ovf_property { + ovf_properties { key = "password" value = "Passw0rd1" } - ovf_property { + ovf_properties { key = "hostname" value = "HelloWorld" } - ovf_property { + ovf_properties { key = "user-data" value = base64encode(data.template_file.userdata_default.rendered) }