Skip to content

Commit

Permalink
Merge pull request #81 from josenk/v1.6.0/ovf_properties
Browse files Browse the repository at this point in the history
V1.6.0/ovf properties
  • Loading branch information
josenk authored Nov 26, 2019
2 parents 221496f + ffba5d5 commit 102c2ce
Show file tree
Hide file tree
Showing 13 changed files with 179 additions and 197 deletions.
82 changes: 8 additions & 74 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -277,7 +210,8 @@ Known issues with vmware_esxi

Version History
---------------
* 1.5.4.Fix bare-metal build when using additional virtual disks.
* 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.
Expand Down
55 changes: 28 additions & 27 deletions esxi/guest-create.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,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}
Expand All @@ -27,7 +27,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)
Expand Down Expand Up @@ -206,28 +208,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"
Expand Down Expand Up @@ -291,20 +287,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")
}
}

Expand All @@ -315,7 +316,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")
}

//
Expand Down
11 changes: 0 additions & 11 deletions esxi/guest-read.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,6 @@ func resourceGUESTRead(d *schema.ResourceData, m interface{}) error {
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)
}

// Do network interfaces
log.Printf("virtual_networks: %q\n", virtual_networks)
nics := make([]map[string]interface{}, 0, 1)
Expand Down
56 changes: 40 additions & 16 deletions esxi/resource_guest.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand All @@ -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 {
Expand Down Expand Up @@ -343,28 +368,27 @@ 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 {
ovf_properties = make(map[string]string)
}

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 {
Expand Down
45 changes: 0 additions & 45 deletions examples/06 CloudInit and Ova Template/userdata.tpl

This file was deleted.

Loading

0 comments on commit 102c2ce

Please sign in to comment.