diff --git a/README.md b/README.md index ac7e2b3..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,8 +217,10 @@ 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. * 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. diff --git a/esxi/guest-create.go b/esxi/guest-create.go index e155a8a..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" @@ -83,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) } } @@ -167,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()) @@ -190,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) } } @@ -275,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()) } } diff --git a/esxi/guest-read.go b/esxi/guest-read.go index 510e402..769f6fb 100644 --- a/esxi/guest-read.go +++ b/esxi/guest-read.go @@ -38,7 +38,10 @@ 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 len(guestinfo) != 0 { + d.Set("guestinfo", guestinfo) + } // Do network interfaces log.Printf("virtual_networks: %q\n", virtual_networks) @@ -223,16 +226,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 7bac31d..6cb69df 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, @@ -246,18 +246,20 @@ func resourceGUESTCreate(d *schema.ResourceData, m interface{}) error { notes := d.Get("notes").(string) power := d.Get("power").(string) - if d.Get("guest_startup_timeout").(int) > 0 { + 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 { + + 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 { + + 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 { 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/main.tf b/examples/06 OVF Properties/main.tf index 6233482..b88e138 100644 --- a/examples/06 OVF Properties/main.tf +++ b/examples/06 OVF Properties/main.tf @@ -18,24 +18,13 @@ data "template_file" "userdata_default" { } 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" + 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) @@ -60,32 +49,4 @@ resource "esxi_guest" "vmtest" { key = "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/go.mod b/go.mod index af85c8c..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.6.0 + 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 b7c0a9b..69fd868 100644 --- a/version +++ b/version @@ -1 +1 @@ -v1.6.0 +v1.6.1 \ No newline at end of file