Merge pull request #7952 from dagnello/vsphere-file-copy

vSphere file resource: extending functionality to copy files in vSphere
This commit is contained in:
James Nugent 2016-08-05 10:59:30 -07:00 committed by GitHub
commit de822d909c
3 changed files with 308 additions and 49 deletions

View File

@ -3,6 +3,7 @@ package vsphere
import ( import (
"fmt" "fmt"
"log" "log"
"strings"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/vmware/govmomi" "github.com/vmware/govmomi"
@ -13,10 +14,14 @@ import (
) )
type file struct { type file struct {
datacenter string sourceDatacenter string
datastore string datacenter string
sourceFile string sourceDatastore string
destinationFile string datastore string
sourceFile string
destinationFile string
createDirectories bool
copyFile bool
} }
func resourceVSphereFile() *schema.Resource { func resourceVSphereFile() *schema.Resource {
@ -30,10 +35,20 @@ func resourceVSphereFile() *schema.Resource {
"datacenter": { "datacenter": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
},
"source_datacenter": {
Type: schema.TypeString,
Optional: true,
ForceNew: true, ForceNew: true,
}, },
"datastore": { "datastore": {
Type: schema.TypeString,
Required: true,
},
"source_datastore": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
ForceNew: true, ForceNew: true,
@ -49,6 +64,11 @@ func resourceVSphereFile() *schema.Resource {
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Required: true,
}, },
"create_directories": {
Type: schema.TypeBool,
Optional: true,
},
}, },
} }
} }
@ -60,10 +80,20 @@ func resourceVSphereFileCreate(d *schema.ResourceData, meta interface{}) error {
f := file{} f := file{}
if v, ok := d.GetOk("source_datacenter"); ok {
f.sourceDatacenter = v.(string)
f.copyFile = true
}
if v, ok := d.GetOk("datacenter"); ok { if v, ok := d.GetOk("datacenter"); ok {
f.datacenter = v.(string) f.datacenter = v.(string)
} }
if v, ok := d.GetOk("source_datastore"); ok {
f.sourceDatastore = v.(string)
f.copyFile = true
}
if v, ok := d.GetOk("datastore"); ok { if v, ok := d.GetOk("datastore"); ok {
f.datastore = v.(string) f.datastore = v.(string)
} else { } else {
@ -82,6 +112,10 @@ func resourceVSphereFileCreate(d *schema.ResourceData, meta interface{}) error {
return fmt.Errorf("destination_file argument is required") return fmt.Errorf("destination_file argument is required")
} }
if v, ok := d.GetOk("create_directories"); ok {
f.createDirectories = v.(bool)
}
err := createFile(client, &f) err := createFile(client, &f)
if err != nil { if err != nil {
return err return err
@ -108,16 +142,53 @@ func createFile(client *govmomi.Client, f *file) error {
return fmt.Errorf("error %s", err) return fmt.Errorf("error %s", err)
} }
dsurl, err := ds.URL(context.TODO(), dc, f.destinationFile) if f.copyFile {
if err != nil { // Copying file from withing vSphere
return err source_dc, err := finder.Datacenter(context.TODO(), f.sourceDatacenter)
if err != nil {
return fmt.Errorf("error %s", err)
}
finder = finder.SetDatacenter(dc)
source_ds, err := getDatastore(finder, f.sourceDatastore)
if err != nil {
return fmt.Errorf("error %s", err)
}
fm := object.NewFileManager(client.Client)
if f.createDirectories {
directoryPathIndex := strings.LastIndex(f.destinationFile, "/")
path := f.destinationFile[0:directoryPathIndex]
err = fm.MakeDirectory(context.TODO(), ds.Path(path), dc, true)
if err != nil {
return fmt.Errorf("error %s", err)
}
}
task, err := fm.CopyDatastoreFile(context.TODO(), source_ds.Path(f.sourceFile), source_dc, ds.Path(f.destinationFile), dc, true)
if err != nil {
return fmt.Errorf("error %s", err)
}
_, err = task.WaitForResult(context.TODO(), nil)
if err != nil {
return fmt.Errorf("error %s", err)
}
} else {
// Uploading file to vSphere
dsurl, err := ds.URL(context.TODO(), dc, f.destinationFile)
if err != nil {
return fmt.Errorf("error %s", err)
}
p := soap.DefaultUpload
err = client.Client.UploadFile(f.sourceFile, dsurl, &p)
if err != nil {
return fmt.Errorf("error %s", err)
}
} }
p := soap.DefaultUpload
err = client.Client.UploadFile(f.sourceFile, dsurl, &p)
if err != nil {
return fmt.Errorf("error %s", err)
}
return nil return nil
} }
@ -126,10 +197,18 @@ func resourceVSphereFileRead(d *schema.ResourceData, meta interface{}) error {
log.Printf("[DEBUG] reading file: %#v", d) log.Printf("[DEBUG] reading file: %#v", d)
f := file{} f := file{}
if v, ok := d.GetOk("source_datacenter"); ok {
f.sourceDatacenter = v.(string)
}
if v, ok := d.GetOk("datacenter"); ok { if v, ok := d.GetOk("datacenter"); ok {
f.datacenter = v.(string) f.datacenter = v.(string)
} }
if v, ok := d.GetOk("source_datastore"); ok {
f.sourceDatastore = v.(string)
}
if v, ok := d.GetOk("datastore"); ok { if v, ok := d.GetOk("datastore"); ok {
f.datastore = v.(string) f.datastore = v.(string)
} else { } else {
@ -179,57 +258,69 @@ func resourceVSphereFileRead(d *schema.ResourceData, meta interface{}) error {
func resourceVSphereFileUpdate(d *schema.ResourceData, meta interface{}) error { func resourceVSphereFileUpdate(d *schema.ResourceData, meta interface{}) error {
log.Printf("[DEBUG] updating file: %#v", d) log.Printf("[DEBUG] updating file: %#v", d)
if d.HasChange("destination_file") {
oldDestinationFile, newDestinationFile := d.GetChange("destination_file")
f := file{}
if v, ok := d.GetOk("datacenter"); ok { if d.HasChange("destination_file") || d.HasChange("datacenter") || d.HasChange("datastore") {
f.datacenter = v.(string) // File needs to be moved, get old and new destination changes
} var oldDataceneter, newDatacenter, oldDatastore, newDatastore, oldDestinationFile, newDestinationFile string
if d.HasChange("datacenter") {
if v, ok := d.GetOk("datastore"); ok { tmpOldDataceneter, tmpNewDatacenter := d.GetChange("datacenter")
f.datastore = v.(string) oldDataceneter = tmpOldDataceneter.(string)
newDatacenter = tmpNewDatacenter.(string)
} else { } else {
return fmt.Errorf("datastore argument is required") if v, ok := d.GetOk("datacenter"); ok {
oldDataceneter = v.(string)
newDatacenter = oldDataceneter
}
} }
if d.HasChange("datastore") {
if v, ok := d.GetOk("source_file"); ok { tmpOldDatastore, tmpNewDatastore := d.GetChange("datastore")
f.sourceFile = v.(string) oldDatastore = tmpOldDatastore.(string)
newDatastore = tmpNewDatastore.(string)
} else { } else {
return fmt.Errorf("source_file argument is required") oldDatastore = d.Get("datastore").(string)
newDatastore = oldDatastore
} }
if d.HasChange("destination_file") {
if v, ok := d.GetOk("destination_file"); ok { tmpOldDestinationFile, tmpNewDestinationFile := d.GetChange("destination_file")
f.destinationFile = v.(string) oldDestinationFile = tmpOldDestinationFile.(string)
newDestinationFile = tmpNewDestinationFile.(string)
} else { } else {
return fmt.Errorf("destination_file argument is required") oldDestinationFile = d.Get("destination_file").(string)
newDestinationFile = oldDestinationFile
} }
// Get old and new dataceter and datastore
client := meta.(*govmomi.Client) client := meta.(*govmomi.Client)
dc, err := getDatacenter(client, f.datacenter) dcOld, err := getDatacenter(client, oldDataceneter)
if err != nil {
return err
}
dcNew, err := getDatacenter(client, newDatacenter)
if err != nil { if err != nil {
return err return err
} }
finder := find.NewFinder(client.Client, true) finder := find.NewFinder(client.Client, true)
finder = finder.SetDatacenter(dc) finder = finder.SetDatacenter(dcOld)
dsOld, err := getDatastore(finder, oldDatastore)
ds, err := getDatastore(finder, f.datastore) if err != nil {
return fmt.Errorf("error %s", err)
}
finder = finder.SetDatacenter(dcNew)
dsNew, err := getDatastore(finder, newDatastore)
if err != nil { if err != nil {
return fmt.Errorf("error %s", err) return fmt.Errorf("error %s", err)
} }
// Move file between old/new dataceter, datastore and path (destination_file)
fm := object.NewFileManager(client.Client) fm := object.NewFileManager(client.Client)
task, err := fm.MoveDatastoreFile(context.TODO(), ds.Path(oldDestinationFile.(string)), dc, ds.Path(newDestinationFile.(string)), dc, true) task, err := fm.MoveDatastoreFile(context.TODO(), dsOld.Path(oldDestinationFile), dcOld, dsNew.Path(newDestinationFile), dcNew, true)
if err != nil { if err != nil {
return err return err
} }
_, err = task.WaitForResult(context.TODO(), nil) _, err = task.WaitForResult(context.TODO(), nil)
if err != nil { if err != nil {
return err return err
} }
} }
return nil return nil

View File

@ -14,7 +14,7 @@ import (
"golang.org/x/net/context" "golang.org/x/net/context"
) )
// Basic file creation // Basic file creation (upload to vSphere)
func TestAccVSphereFile_basic(t *testing.T) { func TestAccVSphereFile_basic(t *testing.T) {
testVmdkFileData := []byte("# Disk DescriptorFile\n") testVmdkFileData := []byte("# Disk DescriptorFile\n")
testVmdkFile := "/tmp/tf_test.vmdk" testVmdkFile := "/tmp/tf_test.vmdk"
@ -55,6 +55,59 @@ func TestAccVSphereFile_basic(t *testing.T) {
os.Remove(testVmdkFile) os.Remove(testVmdkFile)
} }
// Basic file copy within vSphere
func TestAccVSphereFile_basicUploadAndCopy(t *testing.T) {
testVmdkFileData := []byte("# Disk DescriptorFile\n")
sourceFile := "/tmp/tf_test.vmdk"
uploadResourceName := "myfileupload"
copyResourceName := "myfilecopy"
sourceDatacenter := os.Getenv("VSPHERE_DATACENTER")
datacenter := sourceDatacenter
sourceDatastore := os.Getenv("VSPHERE_DATASTORE")
datastore := sourceDatastore
destinationFile := "tf_file_test.vmdk"
sourceFileCopy := "${vsphere_file." + uploadResourceName + ".destination_file}"
destinationFileCopy := "tf_file_test_copy.vmdk"
err := ioutil.WriteFile(sourceFile, testVmdkFileData, 0644)
if err != nil {
t.Errorf("error %s", err)
return
}
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckVSphereFileDestroy,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(
testAccCheckVSphereFileCopyConfig,
uploadResourceName,
datacenter,
datastore,
sourceFile,
destinationFile,
copyResourceName,
datacenter,
datacenter,
datastore,
datastore,
sourceFileCopy,
destinationFileCopy,
),
Check: resource.ComposeTestCheckFunc(
testAccCheckVSphereFileExists("vsphere_file."+uploadResourceName, destinationFile, true),
testAccCheckVSphereFileExists("vsphere_file."+copyResourceName, destinationFileCopy, true),
resource.TestCheckResourceAttr("vsphere_file."+uploadResourceName, "destination_file", destinationFile),
resource.TestCheckResourceAttr("vsphere_file."+copyResourceName, "destination_file", destinationFileCopy),
),
},
},
})
os.Remove(sourceFile)
}
// file creation followed by a rename of file (update) // file creation followed by a rename of file (update)
func TestAccVSphereFile_renamePostCreation(t *testing.T) { func TestAccVSphereFile_renamePostCreation(t *testing.T) {
testVmdkFileData := []byte("# Disk DescriptorFile\n") testVmdkFileData := []byte("# Disk DescriptorFile\n")
@ -67,7 +120,7 @@ func TestAccVSphereFile_renamePostCreation(t *testing.T) {
datacenter := os.Getenv("VSPHERE_DATACENTER") datacenter := os.Getenv("VSPHERE_DATACENTER")
datastore := os.Getenv("VSPHERE_DATASTORE") datastore := os.Getenv("VSPHERE_DATASTORE")
testMethod := "basic" testMethod := "create_upgrade"
resourceName := "vsphere_file." + testMethod resourceName := "vsphere_file." + testMethod
destinationFile := "tf_test_file.vmdk" destinationFile := "tf_test_file.vmdk"
destinationFileMoved := "tf_test_file_moved.vmdk" destinationFileMoved := "tf_test_file_moved.vmdk"
@ -76,7 +129,7 @@ func TestAccVSphereFile_renamePostCreation(t *testing.T) {
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders, Providers: testAccProviders,
CheckDestroy: testAccCheckVSphereFolderDestroy, CheckDestroy: testAccCheckVSphereFileDestroy,
Steps: []resource.TestStep{ Steps: []resource.TestStep{
{ {
Config: fmt.Sprintf( Config: fmt.Sprintf(
@ -113,6 +166,84 @@ func TestAccVSphereFile_renamePostCreation(t *testing.T) {
os.Remove(testVmdkFile) os.Remove(testVmdkFile)
} }
// file upload, then copy, finally the copy is renamed (moved) (update)
func TestAccVSphereFile_uploadAndCopyAndUpdate(t *testing.T) {
testVmdkFileData := []byte("# Disk DescriptorFile\n")
sourceFile := "/tmp/tf_test.vmdk"
uploadResourceName := "myfileupload"
copyResourceName := "myfilecopy"
sourceDatacenter := os.Getenv("VSPHERE_DATACENTER")
datacenter := sourceDatacenter
sourceDatastore := os.Getenv("VSPHERE_DATASTORE")
datastore := sourceDatastore
destinationFile := "tf_file_test.vmdk"
sourceFileCopy := "${vsphere_file." + uploadResourceName + ".destination_file}"
destinationFileCopy := "tf_file_test_copy.vmdk"
destinationFileMoved := "tf_test_file_moved.vmdk"
err := ioutil.WriteFile(sourceFile, testVmdkFileData, 0644)
if err != nil {
t.Errorf("error %s", err)
return
}
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckVSphereFileDestroy,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(
testAccCheckVSphereFileCopyConfig,
uploadResourceName,
datacenter,
datastore,
sourceFile,
destinationFile,
copyResourceName,
datacenter,
datacenter,
datastore,
datastore,
sourceFileCopy,
destinationFileCopy,
),
Check: resource.ComposeTestCheckFunc(
testAccCheckVSphereFileExists("vsphere_file."+uploadResourceName, destinationFile, true),
testAccCheckVSphereFileExists("vsphere_file."+copyResourceName, destinationFileCopy, true),
resource.TestCheckResourceAttr("vsphere_file."+uploadResourceName, "destination_file", destinationFile),
resource.TestCheckResourceAttr("vsphere_file."+copyResourceName, "destination_file", destinationFileCopy),
),
},
{
Config: fmt.Sprintf(
testAccCheckVSphereFileCopyConfig,
uploadResourceName,
datacenter,
datastore,
sourceFile,
destinationFile,
copyResourceName,
datacenter,
datacenter,
datastore,
datastore,
sourceFileCopy,
destinationFileMoved,
),
Check: resource.ComposeTestCheckFunc(
testAccCheckVSphereFileExists("vsphere_file."+uploadResourceName, destinationFile, true),
testAccCheckVSphereFileExists("vsphere_file."+copyResourceName, destinationFileCopy, false),
testAccCheckVSphereFileExists("vsphere_file."+copyResourceName, destinationFileMoved, true),
resource.TestCheckResourceAttr("vsphere_file."+uploadResourceName, "destination_file", destinationFile),
resource.TestCheckResourceAttr("vsphere_file."+copyResourceName, "destination_file", destinationFileMoved),
),
},
},
})
os.Remove(sourceFile)
}
func testAccCheckVSphereFileDestroy(s *terraform.State) error { func testAccCheckVSphereFileDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*govmomi.Client) client := testAccProvider.Meta().(*govmomi.Client)
finder := find.NewFinder(client.Client, true) finder := find.NewFinder(client.Client, true)
@ -201,3 +332,19 @@ resource "vsphere_file" "%s" {
destination_file = "%s" destination_file = "%s"
} }
` `
const testAccCheckVSphereFileCopyConfig = `
resource "vsphere_file" "%s" {
datacenter = "%s"
datastore = "%s"
source_file = "%s"
destination_file = "%s"
}
resource "vsphere_file" "%s" {
source_datacenter = "%s"
datacenter = "%s"
source_datastore = "%s"
datastore = "%s"
source_file = "%s"
destination_file = "%s"
}
`

View File

@ -3,28 +3,49 @@ layout: "vsphere"
page_title: "VMware vSphere: vsphere_file" page_title: "VMware vSphere: vsphere_file"
sidebar_current: "docs-vsphere-resource-file" sidebar_current: "docs-vsphere-resource-file"
description: |- description: |-
Provides a VMware vSphere virtual machine file resource. This can be used to upload files (e.g. vmdk disks) from the Terraform host machine to a remote vSphere. Provides a VMware vSphere virtual machine file resource. This can be used to upload files (e.g. vmdk disks) from the Terraform host machine to a remote vSphere or copy fields withing vSphere.
--- ---
# vsphere\_file # vsphere\_file
Provides a VMware vSphere virtual machine file resource. This can be used to upload files (e.g. vmdk disks) from the Terraform host machine to a remote vSphere. Provides a VMware vSphere virtual machine file resource. This can be used to upload files (e.g. vmdk disks) from the Terraform host machine to a remote vSphere. The file resource can also be used to copy files within vSphere. Files can be copied between Datacenters and/or Datastores.
## Example Usage Updates to file resources will handle moving a file to a new destination (datacenter and/or datastore and/or destination_file). If any source parameter (e.g. `source_datastore`, `source_datacenter` or `source_file`) are changed, this results in a new resource (new file uploaded or copied and old one being deleted).
## Example Usages
**Upload file to vSphere:**
``` ```
resource "vsphere_file" "ubuntu_disk" { resource "vsphere_file" "ubuntu_disk_upload" {
datacenter = "my_datacenter"
datastore = "local" datastore = "local"
source_file = "/home/ubuntu/my_disks/custom_ubuntu.vmdk" source_file = "/home/ubuntu/my_disks/custom_ubuntu.vmdk"
destination_file = "/my_path/disks/custom_ubuntu.vmdk" destination_file = "/my_path/disks/custom_ubuntu.vmdk"
} }
``` ```
**Copy file within vSphere:**
```
resource "vsphere_file" "ubuntu_disk_copy" {
source_datacenter = "my_datacenter"
datacenter = "my_datacenter"
source_datastore = "local"
datastore = "local"
source_file = "/my_path/disks/custom_ubuntu.vmdk"
destination_file = "/my_path/custom_ubuntu_id.vmdk"
}
```
## Argument Reference ## Argument Reference
If `source_datacenter` and `source_datastore` are not provided, the file resource will upload the file from Terraform host. If either `source_datacenter` or `source_datastore` are provided, the file resource will copy from within specified locations in vSphere.
The following arguments are supported: The following arguments are supported:
* `source_file` - (Required) The path to the file on the Terraform host that will be uploaded to vSphere. * `source_file` - (Required) The path to the file being uploaded from the Terraform host to vSphere or copied within vSphere.
* `destination_file` - (Required) The path to where the file should be uploaded to on vSphere. * `destination_file` - (Required) The path to where the file should be uploaded or copied to on vSphere.
* `datacenter` - (Optional) The name of a Datacenter in which the file will be created/uploaded to. * `source_datacenter` - (Optional) The name of a Datacenter in which the file will be copied from.
* `datastore` - (Required) The name of the Datastore in which to create/upload the file to. * `datacenter` - (Optional) The name of a Datacenter in which the file will be uploaded to.
* `source_datastore` - (Optional) The name of the Datastore in which file will be copied from.
* `datastore` - (Required) The name of the Datastore in which to upload the file to.
* `create_directories` - (Optional) Create directories in `destination_file` path parameter if any missing for copy operation. *Note: Directories are not deleted on destroy operation.