From 3313658d7b921ac9f0b5ea09eb25e76276a4ff0e Mon Sep 17 00:00:00 2001 From: Luis Silva <> Date: Wed, 9 May 2018 12:05:13 +0100 Subject: [PATCH] Add attribute to create parent directories when creating a virtual disk --- vsphere/resource_vsphere_virtual_disk.go | 121 ++++++++++++++++-- vsphere/resource_vsphere_virtual_disk_test.go | 72 +++++++++++ website/docs/r/virtual_disk.html.markdown | 7 + 3 files changed, 192 insertions(+), 8 deletions(-) diff --git a/vsphere/resource_vsphere_virtual_disk.go b/vsphere/resource_vsphere_virtual_disk.go index 807c00b9b..eb34b482f 100644 --- a/vsphere/resource_vsphere_virtual_disk.go +++ b/vsphere/resource_vsphere_virtual_disk.go @@ -3,6 +3,7 @@ package vsphere import ( "fmt" "log" + "strings" "errors" "path" @@ -16,12 +17,13 @@ import ( ) type virtualDisk struct { - size int - vmdkPath string - initType string - adapterType string - datacenter string - datastore string + size int + vmdkPath string + initType string + adapterType string + datacenter string + datastore string + createDirectories bool } // Define VirtualDisk args @@ -50,6 +52,12 @@ func resourceVSphereVirtualDisk() *schema.Resource { ForceNew: true, }, + "datastore": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "type": &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -88,8 +96,8 @@ func resourceVSphereVirtualDisk() *schema.Resource { ForceNew: true, }, - "datastore": &schema.Schema{ - Type: schema.TypeString, + "create_directories": &schema.Schema{ + Type: schema.TypeBool, Optional: true, ForceNew: true, }, @@ -125,6 +133,10 @@ func resourceVSphereVirtualDiskCreate(d *schema.ResourceData, meta interface{}) vDisk.datastore = v.(string) } + if v, ok := d.GetOk("create_directories"); ok { + vDisk.createDirectories = v.(bool) + } + finder := find.NewFinder(client.Client, true) dc, err := getDatacenter(client, d.Get("datacenter").(string)) @@ -138,6 +150,27 @@ func resourceVSphereVirtualDiskCreate(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error finding Datastore: %s: %s", vDisk.datastore, err) } + fm := object.NewFileManager(client.Client) + + if vDisk.createDirectories { + directoryPathIndex := strings.LastIndex(vDisk.vmdkPath, "/") + if directoryPathIndex > 0 { + path := vDisk.vmdkPath[0:directoryPathIndex] + log.Printf("[DEBUG] Creating parent directories: %v", ds.Path(path)) + err = fm.MakeDirectory(context.TODO(), ds.Path(path), dc, true) + if err != nil { + log.Printf("[DEBUG] Failed to create parent directories: %v", err) + return err + } + + err = searchForDirectory(client, vDisk.datacenter, vDisk.datastore, path) + if err != nil { + log.Printf("[DEBUG] Failed to find newly created parent directories: %v", err) + return err + } + } + } + err = createHardDisk(client, vDisk.size, ds.Path(vDisk.vmdkPath), vDisk.initType, vDisk.adapterType, vDisk.datacenter) if err != nil { return err @@ -348,3 +381,75 @@ func createHardDisk(client *govmomi.Client, size int, diskPath string, diskType return nil } + +// Searches for the presence of a directory path. +func searchForDirectory(client *govmomi.Client, datacenter string, datastore string, directoryPath string) error { + log.Printf("[DEBUG] Searching for Directory") + finder := find.NewFinder(client.Client, true) + + dc, err := getDatacenter(client, datacenter) + if err != nil { + return fmt.Errorf("Error finding Datacenter: %s: %s", datacenter, err) + } + finder = finder.SetDatacenter(dc) + + ds, err := finder.Datastore(context.TODO(), datastore) + if err != nil { + return fmt.Errorf("Error finding Datastore: %s: %s", datastore, err) + } + + ctx := context.TODO() + b, err := ds.Browser(ctx) + if err != nil { + return err + } + + spec := types.HostDatastoreBrowserSearchSpec{ + Query: []types.BaseFileQuery{&types.FolderFileQuery{}}, + Details: &types.FileQueryFlags{ + FileSize: true, + FileType: true, + Modification: true, + FileOwner: types.NewBool(true), + }, + MatchPattern: []string{path.Base(directoryPath)}, + } + + dsPath := ds.Path(path.Dir(directoryPath)) + task, err := b.SearchDatastore(context.TODO(), dsPath, &spec) + + if err != nil { + log.Printf("[DEBUG] searchForDirectory - could not search datastore for: %v", directoryPath) + return err + } + + info, err := task.WaitForResult(context.TODO(), nil) + if err != nil { + if info == nil || info.Error != nil { + _, ok := info.Error.Fault.(*types.FileNotFound) + if ok { + log.Printf("[DEBUG] searchForDirectory - could not find: %v", directoryPath) + return nil + } + } + + log.Printf("[DEBUG] searchForDirectory - could not search datastore for: %v", directoryPath) + return err + } + + res := info.Result.(types.HostDatastoreBrowserSearchResults) + log.Printf("[DEBUG] num results: %d", len(res.File)) + if len(res.File) == 0 { + log.Printf("[DEBUG] searchForDirectory - could not find: %v", directoryPath) + return nil + } + + if len(res.File) != 1 { + return errors.New("Datastore search did not return exactly one result") + } + + fileInfo := res.File[0] + log.Printf("[DEBUG] searchForDirectory - fileinfo: %#v", fileInfo) + + return nil +} diff --git a/vsphere/resource_vsphere_virtual_disk_test.go b/vsphere/resource_vsphere_virtual_disk_test.go index 39d98bed9..052755594 100644 --- a/vsphere/resource_vsphere_virtual_disk_test.go +++ b/vsphere/resource_vsphere_virtual_disk_test.go @@ -4,12 +4,14 @@ import ( "fmt" "log" "os" + "strings" "testing" "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" "github.com/vmware/govmomi/find" + "github.com/vmware/govmomi/object" "golang.org/x/net/context" ) @@ -51,6 +53,44 @@ func TestAccVSphereVirtualDisk_basic(t *testing.T) { }) } +func TestAccVSphereVirtualDisk_withParents(t *testing.T) { + var datacenterOpt string + var datastoreOpt string + var initTypeOpt string + var adapterTypeOpt string + + rString := acctest.RandString(5) + + if v := os.Getenv("VSPHERE_DATACENTER"); v != "" { + datacenterOpt = v + } + if v := os.Getenv("VSPHERE_DATASTORE"); v != "" { + datastoreOpt = v + } + if v := os.Getenv("VSPHERE_INIT_TYPE"); v != "" { + initTypeOpt += fmt.Sprintf(" type = \"%s\"\n", v) + } else { + initTypeOpt += fmt.Sprintf(" type = \"%s\"\n", "thin") + } + if v := os.Getenv("VSPHERE_ADAPTER_TYPE"); v != "" { + adapterTypeOpt += fmt.Sprintf(" adapter_type = \"%s\"\n", v) + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVSphereVirtualDiskDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckVSphereVirtuaDiskConfig_withParents(rString, initTypeOpt, adapterTypeOpt, datacenterOpt, datastoreOpt), + Check: resource.ComposeTestCheckFunc( + testAccVSphereVirtualDiskExists("vsphere_virtual_disk.foo"), + ), + }, + }, + }) +} + func testAccVSphereVirtualDiskExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[name] @@ -110,6 +150,24 @@ func testAccCheckVSphereVirtualDiskDestroy(s *terraform.State) error { if err == nil { return fmt.Errorf("error %s", err) } + + // Clean up the test parent directory created for evaluation of the attribute "create_directories" + if rs.Primary.Attributes["create_directories"] == "true" { + fm := object.NewFileManager(client.Client) + vmdkPath := rs.Primary.Attributes["vmdk_path"] + directoryPathIndex := strings.LastIndex(vmdkPath, "/") + path := vmdkPath[0:directoryPathIndex] + + task, err := fm.DeleteDatastoreFile(context.TODO(), ds.Path(path), dc) + if err != nil { + return fmt.Errorf("error %s", err) + } + + _, err = task.WaitForResult(context.TODO(), nil) + if err != nil { + return fmt.Errorf("error %s", err) + } + } } return nil @@ -127,3 +185,17 @@ resource "vsphere_virtual_disk" "foo" { } `, rName, initTypeOpt, adapterTypeOpt, datacenterOpt, datastoreOpt) } + +func testAccCheckVSphereVirtuaDiskConfig_withParents(rName, initTypeOpt, adapterTypeOpt, datacenterOpt, datastoreOpt string) string { + return fmt.Sprintf(` +resource "vsphere_virtual_disk" "foo" { + size = 1 + vmdk_path = "tfTestParent-%s/tfTestDisk-%s.vmdk" +%s +%s + datacenter = "%s" + datastore = "%s" + create_directories = "true" +} +`, rName, rName, initTypeOpt, adapterTypeOpt, datacenterOpt, datastoreOpt) +} diff --git a/website/docs/r/virtual_disk.html.markdown b/website/docs/r/virtual_disk.html.markdown index 7e12b63d8..9592297c2 100644 --- a/website/docs/r/virtual_disk.html.markdown +++ b/website/docs/r/virtual_disk.html.markdown @@ -62,3 +62,10 @@ disk controller types. This parameter will be removed in future versions of the vSphere provider. [docs-vsphere-virtual-machine-scsi-type]: /docs/providers/vsphere/r/virtual_machine.html#scsi_type + +* `create_directories` - (Optional) Create directories in `vmdk_path` + path parameter if any missing for disk creation operation. + +~> **NOTE:** Any directory created as part of the operation when +`create_directories` is enabled will not be deleted when the resource is +destroyed.