Adding File Resource for vSphere provider
* Adding File Resource for vSphere provider Allows for file upload to vSphere at specified location. This also includes update for moving or renaming of file resources. * Ensuring required parameters are provided
This commit is contained in:
parent
1c02bf2a7b
commit
e086f6d754
|
@ -46,6 +46,7 @@ func Provider() terraform.ResourceProvider {
|
|||
},
|
||||
|
||||
ResourcesMap: map[string]*schema.Resource{
|
||||
"vsphere_file": resourceVSphereFile(),
|
||||
"vsphere_folder": resourceVSphereFolder(),
|
||||
"vsphere_virtual_machine": resourceVSphereVirtualMachine(),
|
||||
},
|
||||
|
|
|
@ -0,0 +1,309 @@
|
|||
package vsphere
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/vmware/govmomi"
|
||||
"github.com/vmware/govmomi/find"
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type file struct {
|
||||
datacenter string
|
||||
datastore string
|
||||
sourceFile string
|
||||
destinationFile string
|
||||
}
|
||||
|
||||
func resourceVSphereFile() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourceVSphereFileCreate,
|
||||
Read: resourceVSphereFileRead,
|
||||
Update: resourceVSphereFileUpdate,
|
||||
Delete: resourceVSphereFileDelete,
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"datacenter": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"datastore": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"source_file": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"destination_file": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceVSphereFileCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
|
||||
log.Printf("[DEBUG] creating file: %#v", d)
|
||||
client := meta.(*govmomi.Client)
|
||||
|
||||
f := file{}
|
||||
|
||||
if v, ok := d.GetOk("datacenter"); ok {
|
||||
f.datacenter = v.(string)
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("datastore"); ok {
|
||||
f.datastore = v.(string)
|
||||
} else {
|
||||
return fmt.Errorf("datastore argument is required")
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("source_file"); ok {
|
||||
f.sourceFile = v.(string)
|
||||
} else {
|
||||
return fmt.Errorf("source_file argument is required")
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("destination_file"); ok {
|
||||
f.destinationFile = v.(string)
|
||||
} else {
|
||||
return fmt.Errorf("destination_file argument is required")
|
||||
}
|
||||
|
||||
err := createFile(client, &f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.SetId(fmt.Sprintf("[%v] %v/%v", f.datastore, f.datacenter, f.destinationFile))
|
||||
log.Printf("[INFO] Created file: %s", f.destinationFile)
|
||||
|
||||
return resourceVSphereFileRead(d, meta)
|
||||
}
|
||||
|
||||
func createFile(client *govmomi.Client, f *file) 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)
|
||||
|
||||
ds, err := getDatastore(finder, f.datastore)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error %s", err)
|
||||
}
|
||||
|
||||
dsurl, err := ds.URL(context.TODO(), dc, f.destinationFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p := soap.DefaultUpload
|
||||
err = client.Client.UploadFile(f.sourceFile, dsurl, &p)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceVSphereFileRead(d *schema.ResourceData, meta interface{}) error {
|
||||
|
||||
log.Printf("[DEBUG] reading file: %#v", d)
|
||||
f := file{}
|
||||
|
||||
if v, ok := d.GetOk("datacenter"); ok {
|
||||
f.datacenter = v.(string)
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("datastore"); ok {
|
||||
f.datastore = v.(string)
|
||||
} else {
|
||||
return fmt.Errorf("datastore argument is required")
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("source_file"); ok {
|
||||
f.sourceFile = v.(string)
|
||||
} else {
|
||||
return fmt.Errorf("source_file argument is required")
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("destination_file"); ok {
|
||||
f.destinationFile = v.(string)
|
||||
} else {
|
||||
return fmt.Errorf("destination_file argument is required")
|
||||
}
|
||||
|
||||
client := meta.(*govmomi.Client)
|
||||
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)
|
||||
|
||||
ds, err := getDatastore(finder, f.datastore)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error %s", err)
|
||||
}
|
||||
|
||||
_, err = ds.Stat(context.TODO(), f.destinationFile)
|
||||
if err != nil {
|
||||
d.SetId("")
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceVSphereFileUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
|
||||
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 {
|
||||
f.datacenter = v.(string)
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("datastore"); ok {
|
||||
f.datastore = v.(string)
|
||||
} else {
|
||||
return fmt.Errorf("datastore argument is required")
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("source_file"); ok {
|
||||
f.sourceFile = v.(string)
|
||||
} else {
|
||||
return fmt.Errorf("source_file argument is required")
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("destination_file"); ok {
|
||||
f.destinationFile = v.(string)
|
||||
} else {
|
||||
return fmt.Errorf("destination_file argument is required")
|
||||
}
|
||||
|
||||
client := meta.(*govmomi.Client)
|
||||
dc, err := getDatacenter(client, f.datacenter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
finder := find.NewFinder(client.Client, true)
|
||||
finder = finder.SetDatacenter(dc)
|
||||
|
||||
ds, err := getDatastore(finder, f.datastore)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error %s", err)
|
||||
}
|
||||
|
||||
fm := object.NewFileManager(client.Client)
|
||||
task, err := fm.MoveDatastoreFile(context.TODO(), ds.Path(oldDestinationFile.(string)), dc, ds.Path(newDestinationFile.(string)), dc, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = task.WaitForResult(context.TODO(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceVSphereFileDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
|
||||
log.Printf("[DEBUG] deleting file: %#v", d)
|
||||
f := file{}
|
||||
|
||||
if v, ok := d.GetOk("datacenter"); ok {
|
||||
f.datacenter = v.(string)
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("datastore"); ok {
|
||||
f.datastore = v.(string)
|
||||
} else {
|
||||
return fmt.Errorf("datastore argument is required")
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("source_file"); ok {
|
||||
f.sourceFile = v.(string)
|
||||
} else {
|
||||
return fmt.Errorf("source_file argument is required")
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("destination_file"); ok {
|
||||
f.destinationFile = v.(string)
|
||||
} else {
|
||||
return fmt.Errorf("destination_file argument is required")
|
||||
}
|
||||
|
||||
client := meta.(*govmomi.Client)
|
||||
|
||||
err := deleteFile(client, &f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteFile(client *govmomi.Client, f *file) error {
|
||||
|
||||
dc, err := getDatacenter(client, f.datacenter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
finder := find.NewFinder(client.Client, true)
|
||||
finder = finder.SetDatacenter(dc)
|
||||
|
||||
ds, err := getDatastore(finder, f.datastore)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error %s", err)
|
||||
}
|
||||
|
||||
fm := object.NewFileManager(client.Client)
|
||||
task, err := fm.DeleteDatastoreFile(context.TODO(), ds.Path(f.destinationFile), dc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = task.WaitForResult(context.TODO(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getDatastore gets datastore object
|
||||
func getDatastore(f *find.Finder, ds string) (*object.Datastore, error) {
|
||||
|
||||
if ds != "" {
|
||||
dso, err := f.Datastore(context.TODO(), ds)
|
||||
return dso, err
|
||||
} else {
|
||||
dso, err := f.DefaultDatastore(context.TODO())
|
||||
return dso, err
|
||||
}
|
||||
}
|
|
@ -0,0 +1,203 @@
|
|||
package vsphere
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"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 file creation
|
||||
func TestAccVSphereFile_basic(t *testing.T) {
|
||||
testVmdkFileData := []byte("# Disk DescriptorFile\n")
|
||||
testVmdkFile := "/tmp/tf_test.vmdk"
|
||||
err := ioutil.WriteFile(testVmdkFile, testVmdkFileData, 0644)
|
||||
if err != nil {
|
||||
t.Errorf("error %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
datacenter := os.Getenv("VSPHERE_DATACENTER")
|
||||
datastore := os.Getenv("VSPHERE_DATASTORE")
|
||||
testMethod := "basic"
|
||||
resourceName := "vsphere_file." + testMethod
|
||||
destinationFile := "tf_file_test.vmdk"
|
||||
sourceFile := testVmdkFile
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckVSphereFileDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: fmt.Sprintf(
|
||||
testAccCheckVSphereFileConfig,
|
||||
testMethod,
|
||||
datacenter,
|
||||
datastore,
|
||||
sourceFile,
|
||||
destinationFile,
|
||||
),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckVSphereFileExists(resourceName, destinationFile, true),
|
||||
resource.TestCheckResourceAttr(resourceName, "destination_file", destinationFile),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
os.Remove(testVmdkFile)
|
||||
}
|
||||
|
||||
// file creation followed by a rename of file (update)
|
||||
func TestAccVSphereFile_renamePostCreation(t *testing.T) {
|
||||
testVmdkFileData := []byte("# Disk DescriptorFile\n")
|
||||
testVmdkFile := "/tmp/tf_test.vmdk"
|
||||
err := ioutil.WriteFile(testVmdkFile, testVmdkFileData, 0644)
|
||||
if err != nil {
|
||||
t.Errorf("error %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
datacenter := os.Getenv("VSPHERE_DATACENTER")
|
||||
datastore := os.Getenv("VSPHERE_DATASTORE")
|
||||
testMethod := "basic"
|
||||
resourceName := "vsphere_file." + testMethod
|
||||
destinationFile := "tf_test_file.vmdk"
|
||||
destinationFileMoved := "tf_test_file_moved.vmdk"
|
||||
sourceFile := testVmdkFile
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckVSphereFolderDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: fmt.Sprintf(
|
||||
testAccCheckVSphereFileConfig,
|
||||
testMethod,
|
||||
datacenter,
|
||||
datastore,
|
||||
sourceFile,
|
||||
destinationFile,
|
||||
),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckVSphereFileExists(resourceName, destinationFile, true),
|
||||
testAccCheckVSphereFileExists(resourceName, destinationFileMoved, false),
|
||||
resource.TestCheckResourceAttr(resourceName, "destination_file", destinationFile),
|
||||
),
|
||||
},
|
||||
{
|
||||
Config: fmt.Sprintf(
|
||||
testAccCheckVSphereFileConfig,
|
||||
testMethod,
|
||||
datacenter,
|
||||
datastore,
|
||||
sourceFile,
|
||||
destinationFileMoved,
|
||||
),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckVSphereFileExists(resourceName, destinationFile, false),
|
||||
testAccCheckVSphereFileExists(resourceName, destinationFileMoved, true),
|
||||
resource.TestCheckResourceAttr(resourceName, "destination_file", destinationFileMoved),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
os.Remove(testVmdkFile)
|
||||
}
|
||||
|
||||
func testAccCheckVSphereFileDestroy(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_file" {
|
||||
continue
|
||||
}
|
||||
|
||||
dc, err := finder.Datacenter(context.TODO(), rs.Primary.Attributes["datacenter"])
|
||||
if err != nil {
|
||||
return fmt.Errorf("error %s", err)
|
||||
}
|
||||
|
||||
finder = finder.SetDatacenter(dc)
|
||||
|
||||
ds, err := getDatastore(finder, rs.Primary.Attributes["datastore"])
|
||||
if err != nil {
|
||||
return fmt.Errorf("error %s", err)
|
||||
}
|
||||
|
||||
_, err = ds.Stat(context.TODO(), rs.Primary.Attributes["destination_file"])
|
||||
if err != nil {
|
||||
switch e := err.(type) {
|
||||
case object.DatastoreNoSuchFileError:
|
||||
fmt.Printf("Expected error received: %s\n", e.Error())
|
||||
return nil
|
||||
default:
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("File %s still exists", rs.Primary.Attributes["destination_file"])
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCheckVSphereFileExists(n string, df string, exists bool) 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)
|
||||
}
|
||||
finder = finder.SetDatacenter(dc)
|
||||
|
||||
ds, err := getDatastore(finder, rs.Primary.Attributes["datastore"])
|
||||
if err != nil {
|
||||
return fmt.Errorf("error %s", err)
|
||||
}
|
||||
|
||||
_, err = ds.Stat(context.TODO(), df)
|
||||
if err != nil {
|
||||
switch e := err.(type) {
|
||||
case object.DatastoreNoSuchFileError:
|
||||
if exists {
|
||||
return fmt.Errorf("File does not exist: %s", e.Error())
|
||||
}
|
||||
fmt.Printf("Expected error received: %s\n", e.Error())
|
||||
return nil
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
const testAccCheckVSphereFileConfig = `
|
||||
resource "vsphere_file" "%s" {
|
||||
datacenter = "%s"
|
||||
datastore = "%s"
|
||||
source_file = "%s"
|
||||
destination_file = "%s"
|
||||
}
|
||||
`
|
|
@ -35,6 +35,13 @@ resource "vsphere_folder" "frontend" {
|
|||
path = "frontend"
|
||||
}
|
||||
|
||||
# Create a file
|
||||
resource "vsphere_file" "ubuntu_disk" {
|
||||
datastore = "local"
|
||||
source_file = "/home/ubuntu/my_disks/custom_ubuntu.vmdk"
|
||||
destination_file = "/my_path/disks/custom_ubuntu.vmdk"
|
||||
}
|
||||
|
||||
# Create a virtual machine within the folder
|
||||
resource "vsphere_virtual_machine" "web" {
|
||||
name = "terraform-web"
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
layout: "vsphere"
|
||||
page_title: "VMware vSphere: vsphere_file"
|
||||
sidebar_current: "docs-vsphere-resource-file"
|
||||
description: |-
|
||||
Provides a VMware vSphere virtual machine file resource. This can be used to files (e.g. vmdk disks) from Terraform host machine to remote vSphere.
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
# vsphere\_file
|
||||
|
||||
Provides a VMware vSphere virtual machine file resource. This can be used to files (e.g. vmdk disks) from Terraform host machine to remote vSphere.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "vsphere_file" "ubuntu_disk" {
|
||||
datastore = "local"
|
||||
source_file = "/home/ubuntu/my_disks/custom_ubuntu.vmdk"
|
||||
destination_file = "/my_path/disks/custom_ubuntu.vmdk"
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `source_file` - (Required) The path to the file on Terraform host that will be uploaded to vSphere.
|
||||
* `destination_file` - (Required) The path to where the file should be uploaded to on vSphere.
|
||||
* `datacenter` - (Optional) The name of a Datacenter in which the file will be created/uploaded to.
|
||||
* `datastore` - (Required) The name of the Datastore in which to create/upload the file to.
|
Loading…
Reference in New Issue