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.