From 67bfc2faef6b25fd4265d1a7ce796f10f75f55be Mon Sep 17 00:00:00 2001 From: mdeboercw Date: Mon, 16 Nov 2015 10:44:39 -0800 Subject: [PATCH] Added folder handling for folder-qualified vm names Added acceptance test for creation in folders Added 'baseName' as computed schema attribute for convenience Added 'base_name' computed attribute for convenience Added new vsphere folder resource Fixed folder behavior Assure test folders are properly removed Avoid creating recreating search index in loop Fix typeo in vsphere.createFolder Updated website documentation Renamed test folders to be unique across tests Fixes based on acc test findings; code cleanup Added combined folder and vm acc test Restored newline; fixed skipped acc tests Marked 'existing_path' as computed only Removed debug logging from tests Changed folder read to return error --- builtin/providers/vsphere/provider.go | 1 + .../vsphere/resource_vsphere_folder.go | 233 +++++++++++++++ .../vsphere/resource_vsphere_folder_test.go | 279 ++++++++++++++++++ .../resource_vsphere_virtual_machine.go | 85 ++++-- .../resource_vsphere_virtual_machine_test.go | 206 ++++++++++++- .../providers/vsphere/index.html.markdown | 8 +- .../providers/vsphere/r/folder.html.markdown | 28 ++ 7 files changed, 814 insertions(+), 26 deletions(-) create mode 100644 builtin/providers/vsphere/resource_vsphere_folder.go create mode 100644 builtin/providers/vsphere/resource_vsphere_folder_test.go create mode 100644 website/source/docs/providers/vsphere/r/folder.html.markdown diff --git a/builtin/providers/vsphere/provider.go b/builtin/providers/vsphere/provider.go index 9a749a127b..b5a096d303 100644 --- a/builtin/providers/vsphere/provider.go +++ b/builtin/providers/vsphere/provider.go @@ -32,6 +32,7 @@ func Provider() terraform.ResourceProvider { }, ResourcesMap: map[string]*schema.Resource{ + "vsphere_folder": resourceVSphereFolder(), "vsphere_virtual_machine": resourceVSphereVirtualMachine(), }, diff --git a/builtin/providers/vsphere/resource_vsphere_folder.go b/builtin/providers/vsphere/resource_vsphere_folder.go new file mode 100644 index 0000000000..3ed4d52ad5 --- /dev/null +++ b/builtin/providers/vsphere/resource_vsphere_folder.go @@ -0,0 +1,233 @@ +package vsphere + +import ( + "fmt" + "log" + "path" + "strings" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/vmware/govmomi" + "github.com/vmware/govmomi/find" + "github.com/vmware/govmomi/object" + "golang.org/x/net/context" +) + +type folder struct { + datacenter string + existingPath string + path string +} + +func resourceVSphereFolder() *schema.Resource { + return &schema.Resource{ + Create: resourceVSphereFolderCreate, + Read: resourceVSphereFolderRead, + Delete: resourceVSphereFolderDelete, + + Schema: map[string]*schema.Schema{ + "datacenter": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "path": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "existing_path": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceVSphereFolderCreate(d *schema.ResourceData, meta interface{}) error { + + client := meta.(*govmomi.Client) + + f := folder{ + path: strings.TrimRight(d.Get("path").(string), "/"), + } + + if v, ok := d.GetOk("datacenter"); ok { + f.datacenter = v.(string) + } + + createFolder(client, &f) + + d.Set("existing_path", f.existingPath) + d.SetId(fmt.Sprintf("%v/%v", f.datacenter, f.path)) + log.Printf("[INFO] Created folder: %s", f.path) + + return resourceVSphereFolderRead(d, meta) +} + + +func createFolder(client *govmomi.Client, f *folder) error { + + finder := find.NewFinder(client.Client, true) + + dc, err := finder.Datacenter(context.TODO(), f.datacenter) + if err != nil { + return fmt.Errorf("error %s", err) + } + finder = finder.SetDatacenter(dc) + si := object.NewSearchIndex(client.Client) + + dcFolders, err := dc.Folders(context.TODO()) + if err != nil { + return fmt.Errorf("error %s", err) + } + + folder := dcFolders.VmFolder + var workingPath string + + pathParts := strings.Split(f.path, "/") + for _, pathPart := range pathParts { + if len(workingPath) > 0 { + workingPath += "/" + } + workingPath += pathPart + subfolder, err := si.FindByInventoryPath( + context.TODO(), fmt.Sprintf("%v/vm/%v", f.datacenter, workingPath)) + + if err != nil { + return fmt.Errorf("error %s", err) + } else if subfolder == nil { + log.Printf("[DEBUG] folder not found; creating: %s", workingPath) + folder, err = folder.CreateFolder(context.TODO(), pathPart) + if err != nil { + return fmt.Errorf("Failed to create folder at %s; %s", workingPath, err) + } + } else { + log.Printf("[DEBUG] folder already exists: %s", workingPath) + f.existingPath = workingPath + folder = subfolder.(*object.Folder) + } + } + return nil +} + + +func resourceVSphereFolderRead(d *schema.ResourceData, meta interface{}) error { + + log.Printf("[DEBUG] reading folder: %#v", d) + client := meta.(*govmomi.Client) + + dc, err := getDatacenter(client, d.Get("datacenter").(string)) + if err != nil { + return err + } + + finder := find.NewFinder(client.Client, true) + finder = finder.SetDatacenter(dc) + + folder, err := object.NewSearchIndex(client.Client).FindByInventoryPath( + context.TODO(), fmt.Sprintf("%v/vm/%v", d.Get("datacenter").(string), + d.Get("path").(string))) + + if err != nil { + return err + } + + if folder == nil { + d.SetId("") + } + + return nil +} + +func resourceVSphereFolderDelete(d *schema.ResourceData, meta interface{}) error { + + f := folder{ + path: strings.TrimRight(d.Get("path").(string), "/"), + existingPath: d.Get("existing_path").(string), + } + + if v, ok := d.GetOk("datacenter"); ok { + f.datacenter = v.(string) + } + + client := meta.(*govmomi.Client) + + deleteFolder(client, &f) + + d.SetId("") + return nil +} + +func deleteFolder(client *govmomi.Client, f *folder) error { + dc, err := getDatacenter(client, f.datacenter) + if err != nil { + return err + } + var folder *object.Folder + currentPath := f.path + + finder := find.NewFinder(client.Client, true) + finder = finder.SetDatacenter(dc) + si := object.NewSearchIndex(client.Client) + + folderRef, err := si.FindByInventoryPath( + context.TODO(), fmt.Sprintf("%v/vm/%v", f.datacenter, f.path)) + + if err != nil { + return fmt.Errorf("[ERROR] Could not locate folder %s: %v", f.path, err) + } else { + folder = folderRef.(*object.Folder) + } + + log.Printf("[INFO] Deleting empty sub-folders of existing path: %s", f.existingPath) + for currentPath != f.existingPath { + log.Printf("[INFO] Deleting folder: %s", currentPath) + children, err := folder.Children(context.TODO()) + if err != nil { + return err + } + + if len(children) > 0 { + return fmt.Errorf("Folder %s is non-empty and will not be deleted", currentPath) + } else { + log.Printf("[DEBUG] current folder: %#v", folder) + currentPath = path.Dir(currentPath) + if currentPath == "." { + currentPath = "" + } + log.Printf("[INFO] parent path of %s is calculated as %s", f.path, currentPath) + task, err := folder.Destroy(context.TODO()) + if err != nil { + return err + } + err = task.Wait(context.TODO()) + if err != nil { + return err + } + folderRef, err = si.FindByInventoryPath( + context.TODO(), fmt.Sprintf("%v/vm/%v", f.datacenter, currentPath)) + + if err != nil { + return err + } else if folderRef != nil { + folder = folderRef.(*object.Folder) + } + } + } + return nil +} + +// getDatacenter gets datacenter object +func getDatacenter(c *govmomi.Client, dc string) (*object.Datacenter, error) { + finder := find.NewFinder(c.Client, true) + if dc != "" { + d, err := finder.Datacenter(context.TODO(), dc) + return d, err + } else { + d, err := finder.DefaultDatacenter(context.TODO()) + return d, err + } +} diff --git a/builtin/providers/vsphere/resource_vsphere_folder_test.go b/builtin/providers/vsphere/resource_vsphere_folder_test.go new file mode 100644 index 0000000000..c8dd9828a4 --- /dev/null +++ b/builtin/providers/vsphere/resource_vsphere_folder_test.go @@ -0,0 +1,279 @@ +package vsphere + +import ( + "fmt" + "os" + "testing" + + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/vmware/govmomi" + "github.com/vmware/govmomi/find" + "github.com/vmware/govmomi/object" + "golang.org/x/net/context" +) + +// Basic top-level folder creation +func TestAccVSphereFolder_basic(t *testing.T) { + var f folder + datacenter := os.Getenv("VSPHERE_DATACENTER") + testMethod := "basic" + resourceName := "vsphere_folder." + testMethod + path := "tf_test_basic" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVSphereFolderDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: fmt.Sprintf( + testAccCheckVSphereFolderConfig, + testMethod, + path, + datacenter, + ), + Check: resource.ComposeTestCheckFunc( + testAccCheckVSphereFolderExists(resourceName, &f), + resource.TestCheckResourceAttr( + resourceName, "path", path), + resource.TestCheckResourceAttr( + resourceName, "existing_path", ""), + ), + }, + }, + }) +} + +func TestAccVSphereFolder_nested(t *testing.T) { + + var f folder + datacenter := os.Getenv("VSPHERE_DATACENTER") + testMethod := "nested" + resourceName := "vsphere_folder." + testMethod + path := "tf_test_nested/tf_test_folder" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVSphereFolderDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: fmt.Sprintf( + testAccCheckVSphereFolderConfig, + testMethod, + path, + datacenter, + ), + Check: resource.ComposeTestCheckFunc( + testAccCheckVSphereFolderExists(resourceName, &f), + resource.TestCheckResourceAttr( + resourceName, "path", path), + resource.TestCheckResourceAttr( + resourceName, "existing_path", ""), + ), + }, + }, + }) +} + +func TestAccVSphereFolder_dontDeleteExisting(t *testing.T) { + + var f folder + datacenter := os.Getenv("VSPHERE_DATACENTER") + testMethod := "dontDeleteExisting" + resourceName := "vsphere_folder." + testMethod + existingPath := "tf_test_dontDeleteExisting/tf_existing" + path := existingPath + "/tf_nested/tf_test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: resource.ComposeTestCheckFunc( + assertVSphereFolderExists(datacenter, existingPath), + removeVSphereFolder(datacenter, existingPath, ""), + ), + Steps: []resource.TestStep{ + resource.TestStep{ + PreConfig: func() { + createVSphereFolder(datacenter, existingPath) + }, + Config: fmt.Sprintf( + testAccCheckVSphereFolderConfig, + testMethod, + path, + datacenter, + ), + Check: resource.ComposeTestCheckFunc( + testAccCheckVSphereFolderExistingPathExists(resourceName, &f), + resource.TestCheckResourceAttr( + resourceName, "path", path), + resource.TestCheckResourceAttr( + resourceName, "existing_path", existingPath), + ), + }, + }, + }) +} + +func testAccCheckVSphereFolderDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*govmomi.Client) + finder := find.NewFinder(client.Client, true) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "vsphere_folder" { + continue + } + + dc, err := finder.Datacenter(context.TODO(), rs.Primary.Attributes["datacenter"]) + if err != nil { + return fmt.Errorf("error %s", err) + } + + dcFolders, err := dc.Folders(context.TODO()) + if err != nil { + return fmt.Errorf("error %s", err) + } + + _, err = object.NewSearchIndex(client.Client).FindChild(context.TODO(), dcFolders.VmFolder, rs.Primary.Attributes["path"]) + if err == nil { + return fmt.Errorf("Record still exists") + } + } + + return nil +} + +func testAccCheckVSphereFolderExists(n string, f *folder) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Resource not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + client := testAccProvider.Meta().(*govmomi.Client) + finder := find.NewFinder(client.Client, true) + + dc, err := finder.Datacenter(context.TODO(), rs.Primary.Attributes["datacenter"]) + if err != nil { + return fmt.Errorf("error %s", err) + } + + dcFolders, err := dc.Folders(context.TODO()) + if err != nil { + return fmt.Errorf("error %s", err) + } + + _, err = object.NewSearchIndex(client.Client).FindChild(context.TODO(), dcFolders.VmFolder, rs.Primary.Attributes["path"]) + + + *f = folder{ + path: rs.Primary.Attributes["path"], + } + + return nil + } +} + +func testAccCheckVSphereFolderExistingPathExists(n string, f *folder) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Resource %s not found in %#v", n, s.RootModule().Resources) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + client := testAccProvider.Meta().(*govmomi.Client) + finder := find.NewFinder(client.Client, true) + + dc, err := finder.Datacenter(context.TODO(), rs.Primary.Attributes["datacenter"]) + if err != nil { + return fmt.Errorf("error %s", err) + } + + dcFolders, err := dc.Folders(context.TODO()) + if err != nil { + return fmt.Errorf("error %s", err) + } + + _, err = object.NewSearchIndex(client.Client).FindChild(context.TODO(), dcFolders.VmFolder, rs.Primary.Attributes["existing_path"]) + + + *f = folder{ + path: rs.Primary.Attributes["path"], + } + + return nil + } +} + +func assertVSphereFolderExists(datacenter string, folder_name string) resource.TestCheckFunc { + + return func(s *terraform.State) error { + client := testAccProvider.Meta().(*govmomi.Client) + folder, err := object.NewSearchIndex(client.Client).FindByInventoryPath( + context.TODO(), fmt.Sprintf("%v/vm/%v", datacenter, folder_name)) + if err != nil { + return fmt.Errorf("Error: %s", err) + } else if folder == nil { + return fmt.Errorf("Folder %s does not exist!", folder_name) + } + + return nil + } +} + +func createVSphereFolder(datacenter string, folder_name string) error { + + client := testAccProvider.Meta().(*govmomi.Client) + + f := folder{path: folder_name, datacenter: datacenter,} + + folder, err := object.NewSearchIndex(client.Client).FindByInventoryPath( + context.TODO(), fmt.Sprintf("%v/vm/%v", datacenter, folder_name)) + if err != nil { + return fmt.Errorf("error %s", err) + } + + if folder == nil { + createFolder(client, &f) + } else { + return fmt.Errorf("Folder %s already exists", folder_name) + } + + return nil +} + +func removeVSphereFolder(datacenter string, folder_name string, existing_path string) resource.TestCheckFunc { + + f := folder{path: folder_name, datacenter: datacenter, existingPath: existing_path,} + + return func(s *terraform.State) error { + + client := testAccProvider.Meta().(*govmomi.Client) + // finder := find.NewFinder(client.Client, true) + + folder, _ := object.NewSearchIndex(client.Client).FindByInventoryPath( + context.TODO(), fmt.Sprintf("%v/vm/%v", datacenter, folder_name)) + if folder != nil { + deleteFolder(client, &f) + } + + return nil + } +} + +const testAccCheckVSphereFolderConfig = ` +resource "vsphere_folder" "%s" { + path = "%s" + datacenter = "%s" +} +` \ No newline at end of file diff --git a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go index 4fbd66b891..3ecddfeb66 100644 --- a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go +++ b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go @@ -42,6 +42,7 @@ type hardDisk struct { type virtualMachine struct { name string + folder string datacenter string cluster string resourcePool string @@ -59,6 +60,18 @@ type virtualMachine struct { customConfigurations map[string](types.AnyType) } +func (v virtualMachine) Path() string { + return vmPath(v.folder, v.name) +} + +func vmPath(folder string, name string) string { + var path string + if len(folder) > 0 { + path += folder + "/" + } + return path + name +} + func resourceVSphereVirtualMachine() *schema.Resource { return &schema.Resource{ Create: resourceVSphereVirtualMachineCreate, @@ -72,6 +85,12 @@ func resourceVSphereVirtualMachine() *schema.Resource { ForceNew: true, }, + "folder": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "vcpu": &schema.Schema{ Type: schema.TypeInt, Required: true, @@ -228,6 +247,10 @@ func resourceVSphereVirtualMachineCreate(d *schema.ResourceData, meta interface{ memoryMb: int64(d.Get("memory").(int)), } + if v, ok := d.GetOk("folder"); ok { + vm.folder = v.(string) + } + if v, ok := d.GetOk("datacenter"); ok { vm.datacenter = v.(string) } @@ -344,7 +367,7 @@ func resourceVSphereVirtualMachineCreate(d *schema.ResourceData, meta interface{ stateConf := &resource.StateChangeConf{ Pending: []string{"pending"}, Target: "active", - Refresh: waitForNetworkingActive(client, vm.datacenter, vm.name), + Refresh: waitForNetworkingActive(client, vm.datacenter, vm.Path()), Timeout: 600 * time.Second, Delay: time.Duration(v.(int)) * time.Second, MinTimeout: 2 * time.Second, @@ -356,13 +379,15 @@ func resourceVSphereVirtualMachineCreate(d *schema.ResourceData, meta interface{ } } } - d.SetId(vm.name) + d.SetId(vm.Path()) log.Printf("[INFO] Created virtual machine: %s", d.Id()) return resourceVSphereVirtualMachineRead(d, meta) } func resourceVSphereVirtualMachineRead(d *schema.ResourceData, meta interface{}) error { + + log.Printf("[DEBUG] reading virtual machine: %#v", d) client := meta.(*govmomi.Client) dc, err := getDatacenter(client, d.Get("datacenter").(string)) if err != nil { @@ -371,9 +396,8 @@ func resourceVSphereVirtualMachineRead(d *schema.ResourceData, meta interface{}) finder := find.NewFinder(client.Client, true) finder = finder.SetDatacenter(dc) - vm, err := finder.VirtualMachine(context.TODO(), d.Get("name").(string)) + vm, err := finder.VirtualMachine(context.TODO(), d.Id()) if err != nil { - log.Printf("[ERROR] Virtual machine not found: %s", d.Get("name").(string)) d.SetId("") return nil } @@ -458,7 +482,7 @@ func resourceVSphereVirtualMachineDelete(d *schema.ResourceData, meta interface{ finder := find.NewFinder(client.Client, true) finder = finder.SetDatacenter(dc) - vm, err := finder.VirtualMachine(context.TODO(), d.Get("name").(string)) + vm, err := finder.VirtualMachine(context.TODO(), vmPath(d.Get("folder").(string), d.Get("name").(string))) if err != nil { return err } @@ -522,18 +546,6 @@ func waitForNetworkingActive(client *govmomi.Client, datacenter, name string) re } } -// getDatacenter gets datacenter object -func getDatacenter(c *govmomi.Client, dc string) (*object.Datacenter, error) { - finder := find.NewFinder(c.Client, true) - if dc != "" { - d, err := finder.Datacenter(context.TODO(), dc) - return d, err - } else { - d, err := finder.DefaultDatacenter(context.TODO()) - return d, err - } -} - // addHardDisk adds a new Hard Disk to the VirtualMachine. func addHardDisk(vm *object.VirtualMachine, size, iops int64, diskType string) error { devices, err := vm.Device(context.TODO()) @@ -766,6 +778,7 @@ func findDatastore(c *govmomi.Client, sps types.StoragePlacementSpec) (*object.D // createVirtualMchine creates a new VirtualMachine. func (vm *virtualMachine) createVirtualMachine(c *govmomi.Client) error { dc, err := getDatacenter(c, vm.datacenter) + if err != nil { return err } @@ -798,6 +811,21 @@ func (vm *virtualMachine) createVirtualMachine(c *govmomi.Client) error { return err } + log.Printf("[DEBUG] folder: %#v", vm.folder) + folder := dcFolders.VmFolder + if len(vm.folder) > 0 { + si := object.NewSearchIndex(c.Client) + folderRef, err := si.FindByInventoryPath( + context.TODO(), fmt.Sprintf("%v/vm/%v", vm.datacenter, vm.folder)) + if err != nil { + return fmt.Errorf("Error reading folder %s: %s", vm.folder, err) + } else if folderRef == nil { + return fmt.Errorf("Cannot find folder %s", vm.folder) + } else { + folder = folderRef.(*object.Folder) + } + } + // network networkDevices := []types.BaseVirtualDeviceConfigSpec{} for _, network := range vm.networkInterfaces { @@ -886,7 +914,7 @@ func (vm *virtualMachine) createVirtualMachine(c *govmomi.Client) error { }) configSpec.Files = &types.VirtualMachineFileInfo{VmPathName: fmt.Sprintf("[%s]", mds.Name)} - task, err := dcFolders.VmFolder.CreateVM(context.TODO(), configSpec, resourcePool, nil) + task, err := folder.CreateVM(context.TODO(), configSpec, resourcePool, nil) if err != nil { log.Printf("[ERROR] %s", err) } @@ -896,7 +924,7 @@ func (vm *virtualMachine) createVirtualMachine(c *govmomi.Client) error { log.Printf("[ERROR] %s", err) } - newVM, err := finder.VirtualMachine(context.TODO(), vm.name) + newVM, err := finder.VirtualMachine(context.TODO(), vm.Path()) if err != nil { return err } @@ -954,6 +982,21 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error { if err != nil { return err } + + log.Printf("[DEBUG] folder: %#v", vm.folder) + folder := dcFolders.VmFolder + if len(vm.folder) > 0 { + si := object.NewSearchIndex(c.Client) + folderRef, err := si.FindByInventoryPath( + context.TODO(), fmt.Sprintf("%v/vm/%v", vm.datacenter, vm.folder)) + if err != nil { + return fmt.Errorf("Error reading folder %s: %s", vm.folder, err) + } else if folderRef == nil { + return fmt.Errorf("Cannot find folder %s", vm.folder) + } else { + folder = folderRef.(*object.Folder) + } + } var datastore *object.Datastore if vm.datastore == "" { @@ -1084,7 +1127,7 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error { } log.Printf("[DEBUG] clone spec: %v", cloneSpec) - task, err := template.Clone(context.TODO(), dcFolders.VmFolder, vm.name, cloneSpec) + task, err := template.Clone(context.TODO(), folder, vm.name, cloneSpec) if err != nil { return err } @@ -1094,7 +1137,7 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error { return err } - newVM, err := finder.VirtualMachine(context.TODO(), vm.name) + newVM, err := finder.VirtualMachine(context.TODO(), vm.Path()) if err != nil { return err } diff --git a/builtin/providers/vsphere/resource_vsphere_virtual_machine_test.go b/builtin/providers/vsphere/resource_vsphere_virtual_machine_test.go index 130523a47b..d92a4119ea 100644 --- a/builtin/providers/vsphere/resource_vsphere_virtual_machine_test.go +++ b/builtin/providers/vsphere/resource_vsphere_virtual_machine_test.go @@ -191,6 +191,140 @@ func TestAccVSphereVirtualMachine_custom_configs(t *testing.T) { }) } +func TestAccVSphereVirtualMachine_createInExistingFolder(t *testing.T) { + var vm virtualMachine + var locationOpt string + var datastoreOpt string + var datacenter string + + folder := "tf_test_createInExistingFolder" + + if v := os.Getenv("VSPHERE_DATACENTER"); v != "" { + locationOpt += fmt.Sprintf(" datacenter = \"%s\"\n", v) + datacenter = v + } + if v := os.Getenv("VSPHERE_CLUSTER"); v != "" { + locationOpt += fmt.Sprintf(" cluster = \"%s\"\n", v) + } + if v := os.Getenv("VSPHERE_RESOURCE_POOL"); v != "" { + locationOpt += fmt.Sprintf(" resource_pool = \"%s\"\n", v) + } + if v := os.Getenv("VSPHERE_DATASTORE"); v != "" { + datastoreOpt = fmt.Sprintf(" datastore = \"%s\"\n", v) + } + template := os.Getenv("VSPHERE_TEMPLATE") + label := os.Getenv("VSPHERE_NETWORK_LABEL_DHCP") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: resource.ComposeTestCheckFunc( + testAccCheckVSphereVirtualMachineDestroy, + removeVSphereFolder(datacenter, folder, ""), + ), + Steps: []resource.TestStep{ + resource.TestStep{ + PreConfig: func() { createVSphereFolder(datacenter, folder) }, + Config: fmt.Sprintf( + testAccCheckVSphereVirtualMachineConfig_createInFolder, + folder, + locationOpt, + label, + datastoreOpt, + template, + ), + Check: resource.ComposeTestCheckFunc( + testAccCheckVSphereVirtualMachineExists("vsphere_virtual_machine.folder", &vm), + resource.TestCheckResourceAttr( + "vsphere_virtual_machine.folder", "name", "terraform-test-folder"), + resource.TestCheckResourceAttr( + "vsphere_virtual_machine.folder", "folder", folder), + resource.TestCheckResourceAttr( + "vsphere_virtual_machine.folder", "vcpu", "2"), + resource.TestCheckResourceAttr( + "vsphere_virtual_machine.folder", "memory", "4096"), + resource.TestCheckResourceAttr( + "vsphere_virtual_machine.folder", "disk.#", "1"), + resource.TestCheckResourceAttr( + "vsphere_virtual_machine.folder", "disk.0.template", template), + resource.TestCheckResourceAttr( + "vsphere_virtual_machine.folder", "network_interface.#", "1"), + resource.TestCheckResourceAttr( + "vsphere_virtual_machine.folder", "network_interface.0.label", label), + ), + }, + }, + }) +} + +func TestAccVSphereVirtualMachine_createWithFolder(t *testing.T) { + var vm virtualMachine + var f folder + var locationOpt string + var folderLocationOpt string + var datastoreOpt string + + folder := "tf_test_createWithFolder" + + if v := os.Getenv("VSPHERE_DATACENTER"); v != "" { + folderLocationOpt = fmt.Sprintf(" datacenter = \"%s\"\n", v) + locationOpt += folderLocationOpt + } + if v := os.Getenv("VSPHERE_CLUSTER"); v != "" { + locationOpt += fmt.Sprintf(" cluster = \"%s\"\n", v) + } + if v := os.Getenv("VSPHERE_RESOURCE_POOL"); v != "" { + locationOpt += fmt.Sprintf(" resource_pool = \"%s\"\n", v) + } + if v := os.Getenv("VSPHERE_DATASTORE"); v != "" { + datastoreOpt = fmt.Sprintf(" datastore = \"%s\"\n", v) + } + template := os.Getenv("VSPHERE_TEMPLATE") + label := os.Getenv("VSPHERE_NETWORK_LABEL_DHCP") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: resource.ComposeTestCheckFunc( + testAccCheckVSphereVirtualMachineDestroy, + testAccCheckVSphereFolderDestroy, + ), + Steps: []resource.TestStep{ + resource.TestStep{ + Config: fmt.Sprintf( + testAccCheckVSphereVirtualMachineConfig_createWithFolder, + folder, + folderLocationOpt, + locationOpt, + label, + datastoreOpt, + template, + ), + Check: resource.ComposeTestCheckFunc( + testAccCheckVSphereVirtualMachineExists("vsphere_virtual_machine.with_folder", &vm), + testAccCheckVSphereFolderExists("vsphere_folder.with_folder", &f), + resource.TestCheckResourceAttr( + "vsphere_virtual_machine.with_folder", "name", "terraform-test-with-folder"), + // resource.TestCheckResourceAttr( + // "vsphere_virtual_machine.with_folder", "folder", folder), + resource.TestCheckResourceAttr( + "vsphere_virtual_machine.with_folder", "vcpu", "2"), + resource.TestCheckResourceAttr( + "vsphere_virtual_machine.with_folder", "memory", "4096"), + resource.TestCheckResourceAttr( + "vsphere_virtual_machine.with_folder", "disk.#", "1"), + resource.TestCheckResourceAttr( + "vsphere_virtual_machine.with_folder", "disk.0.template", template), + resource.TestCheckResourceAttr( + "vsphere_virtual_machine.with_folder", "network_interface.#", "1"), + resource.TestCheckResourceAttr( + "vsphere_virtual_machine.with_folder", "network_interface.0.label", label), + ), + }, + }, + }) +} + func testAccCheckVSphereVirtualMachineDestroy(s *terraform.State) error { client := testAccProvider.Meta().(*govmomi.Client) finder := find.NewFinder(client.Client, true) @@ -210,7 +344,21 @@ func testAccCheckVSphereVirtualMachineDestroy(s *terraform.State) error { return fmt.Errorf("error %s", err) } - _, err = object.NewSearchIndex(client.Client).FindChild(context.TODO(), dcFolders.VmFolder, rs.Primary.Attributes["name"]) + + folder := dcFolders.VmFolder + if len(rs.Primary.Attributes["folder"]) > 0 { + si := object.NewSearchIndex(client.Client) + folderRef, err := si.FindByInventoryPath( + context.TODO(), fmt.Sprintf("%v/vm/%v", rs.Primary.Attributes["datacenter"], rs.Primary.Attributes["folder"])) + if err != nil { + return err + } else if folderRef != nil { + folder = folderRef.(*object.Folder) + } + } + + _, err = object.NewSearchIndex(client.Client).FindChild(context.TODO(), folder, rs.Primary.Attributes["name"]) + if err == nil { return fmt.Errorf("Record still exists") } @@ -306,9 +454,9 @@ func testAccCheckVSphereVirtualMachineExistsHasCustomConfig(n string, vm *virtua return nil } } + func testAccCheckVSphereVirtualMachineExists(n string, vm *virtualMachine) resource.TestCheckFunc { return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not found: %s", n) @@ -331,14 +479,26 @@ func testAccCheckVSphereVirtualMachineExists(n string, vm *virtualMachine) resou return fmt.Errorf("error %s", err) } - _, err = object.NewSearchIndex(client.Client).FindChild(context.TODO(), dcFolders.VmFolder, rs.Primary.Attributes["name"]) + folder := dcFolders.VmFolder + if len(rs.Primary.Attributes["folder"]) > 0 { + si := object.NewSearchIndex(client.Client) + folderRef, err := si.FindByInventoryPath( + context.TODO(), fmt.Sprintf("%v/vm/%v", rs.Primary.Attributes["datacenter"], rs.Primary.Attributes["folder"])) + if err != nil { + return err + } else if folderRef != nil { + folder = folderRef.(*object.Folder) + } + } + + _, err = object.NewSearchIndex(client.Client).FindChild(context.TODO(), folder, rs.Primary.Attributes["name"]) + *vm = virtualMachine{ name: rs.Primary.ID, } return nil - } } @@ -401,3 +561,41 @@ resource "vsphere_virtual_machine" "car" { } } ` + +const testAccCheckVSphereVirtualMachineConfig_createInFolder = ` +resource "vsphere_virtual_machine" "folder" { + name = "terraform-test-folder" + folder = "%s" +%s + vcpu = 2 + memory = 4096 + network_interface { + label = "%s" + } + disk { +%s + template = "%s" + } +} +` + +const testAccCheckVSphereVirtualMachineConfig_createWithFolder = ` +resource "vsphere_folder" "with_folder" { + path = "%s" +%s +} +resource "vsphere_virtual_machine" "with_folder" { + name = "terraform-test-with-folder" + folder = "${vsphere_folder.with_folder.path}" +%s + vcpu = 2 + memory = 4096 + network_interface { + label = "%s" + } + disk { +%s + template = "%s" + } +} +` \ No newline at end of file diff --git a/website/source/docs/providers/vsphere/index.html.markdown b/website/source/docs/providers/vsphere/index.html.markdown index 8cacfd36b9..428d798f2a 100644 --- a/website/source/docs/providers/vsphere/index.html.markdown +++ b/website/source/docs/providers/vsphere/index.html.markdown @@ -30,9 +30,15 @@ provider "vsphere" { vsphere_server = "${var.vsphere_server}" } -# Create a virtual machine +# Create a folder +resource "vsphere_folder" "frontend" { + path = "frontend" +} + +# Create a virtual machine within the folder resource "vsphere_virtual_machine" "web" { name = "terraform_web" + folder = "${vsphere_folder.frontend.path}" vcpu = 2 memory = 4096 diff --git a/website/source/docs/providers/vsphere/r/folder.html.markdown b/website/source/docs/providers/vsphere/r/folder.html.markdown new file mode 100644 index 0000000000..9825a10edb --- /dev/null +++ b/website/source/docs/providers/vsphere/r/folder.html.markdown @@ -0,0 +1,28 @@ +--- +layout: "vsphere" +page_title: "VMware vSphere: vsphere_folder" +sidebar_current: "docs-vsphere-resource-folder" +description: |- + Provides a VMware vSphere virtual machine folder resource. This can be used to create and delete virtual machine folders. +--- + +# vsphere\_virtual\_machine + +Provides a VMware vSphere virtual machine folder resource. This can be used to create and delete virtual machine folders. + +## Example Usage + +``` +resource "vsphere_folder" "web" { + path = "terraform_web_folder" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `path` - (Required) The path of the folder to be created (relative to the datacenter root); should not begin or end with a "/" +* `datacenter` - (Optional) The name of a Datacenter in which the folder will be created +* `existing_path` - (Computed) The path of any parent folder segments which existed at the time this folder was created; on a +destroy action, the (pre-) existing path is not removed.