Skip to content

Commit

Permalink
Merge pull request #3785 from hmrc/master
Browse files Browse the repository at this point in the history
VMWare vCloud Director Support
  • Loading branch information
phinze committed Dec 3, 2015
2 parents 895dd29 + aec94b1 commit 9e68c34
Show file tree
Hide file tree
Showing 24 changed files with 2,347 additions and 0 deletions.
12 changes: 12 additions & 0 deletions builtin/bins/provider-vcd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package main

import (
"github.com/hashicorp/terraform/builtin/providers/vcd"
"github.com/hashicorp/terraform/plugin"
)

func main() {
plugin.Serve(&plugin.ServeOpts{
ProviderFunc: vcd.Provider,
})
}
40 changes: 40 additions & 0 deletions builtin/providers/vcd/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package vcd

import (
"fmt"
"net/url"

"github.com/hmrc/vmware-govcd"
)

type Config struct {
User string
Password string
Org string
Href string
VDC string
MaxRetryTimeout int
}

type VCDClient struct {
*govcd.VCDClient
MaxRetryTimeout int
}

func (c *Config) Client() (*VCDClient, error) {
u, err := url.ParseRequestURI(c.Href)
if err != nil {
return nil, fmt.Errorf("Something went wrong: %s", err)
}

vcdclient := &VCDClient{
govcd.NewVCDClient(*u),
c.MaxRetryTimeout}
org, vcd, err := vcdclient.Authenticate(c.User, c.Password, c.Org, c.VDC)
if err != nil {
return nil, fmt.Errorf("Something went wrong: %s", err)
}
vcdclient.Org = org
vcdclient.OrgVdc = vcd
return vcdclient, nil
}
78 changes: 78 additions & 0 deletions builtin/providers/vcd/provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package vcd

import (
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)

// Provider returns a terraform.ResourceProvider.
func Provider() terraform.ResourceProvider {
return &schema.Provider{
Schema: map[string]*schema.Schema{
"user": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("VCD_USER", nil),
Description: "The user name for vcd API operations.",
},

"password": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("VCD_PASSWORD", nil),
Description: "The user password for vcd API operations.",
},

"org": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("VCD_ORG", nil),
Description: "The vcd org for API operations",
},

"url": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("VCD_URL", nil),
Description: "The vcd url for vcd API operations.",
},

"vdc": &schema.Schema{
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("VCD_VDC", ""),
Description: "The name of the VDC to run operations on",
},

"maxRetryTimeout": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("VCD_MAX_RETRY_TIMEOUT", 60),
Description: "Max num seconds to wait for successful response when operating on resources within vCloud (defaults to 60)",
},
},

ResourcesMap: map[string]*schema.Resource{
"vcd_network": resourceVcdNetwork(),
"vcd_vapp": resourceVcdVApp(),
"vcd_firewall_rules": resourceVcdFirewallRules(),
"vcd_dnat": resourceVcdDNAT(),
"vcd_snat": resourceVcdSNAT(),
},

ConfigureFunc: providerConfigure,
}
}

func providerConfigure(d *schema.ResourceData) (interface{}, error) {
config := Config{
User: d.Get("user").(string),
Password: d.Get("password").(string),
Org: d.Get("org").(string),
Href: d.Get("url").(string),
VDC: d.Get("vdc").(string),
MaxRetryTimeout: d.Get("maxRetryTimeout").(int),
}

return config.Client()
}
50 changes: 50 additions & 0 deletions builtin/providers/vcd/provider_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package vcd

import (
"os"
"testing"

"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)

var testAccProviders map[string]terraform.ResourceProvider
var testAccProvider *schema.Provider

func init() {
testAccProvider = Provider().(*schema.Provider)
testAccProviders = map[string]terraform.ResourceProvider{
"vcd": testAccProvider,
}
}

func TestProvider(t *testing.T) {
if err := Provider().(*schema.Provider).InternalValidate(); err != nil {
t.Fatalf("err: %s", err)
}
}

func TestProvider_impl(t *testing.T) {
var _ terraform.ResourceProvider = Provider()
}

func testAccPreCheck(t *testing.T) {
if v := os.Getenv("VCD_USER"); v == "" {
t.Fatal("VCD_USER must be set for acceptance tests")
}
if v := os.Getenv("VCD_PASSWORD"); v == "" {
t.Fatal("VCD_PASSWORD must be set for acceptance tests")
}
if v := os.Getenv("VCD_ORG"); v == "" {
t.Fatal("VCD_ORG must be set for acceptance tests")
}
if v := os.Getenv("VCD_URL"); v == "" {
t.Fatal("VCD_URL must be set for acceptance tests")
}
if v := os.Getenv("VCD_EDGE_GATEWAY"); v == "" {
t.Fatal("VCD_EDGE_GATEWAY must be set for acceptance tests")
}
if v := os.Getenv("VCD_VDC"); v == "" {
t.Fatal("VCD_VDC must be set for acceptance tests")
}
}
135 changes: 135 additions & 0 deletions builtin/providers/vcd/resource_vcd_dnat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package vcd

import (
"fmt"
"github.com/hashicorp/terraform/helper/schema"
)

func resourceVcdDNAT() *schema.Resource {
return &schema.Resource{
Create: resourceVcdDNATCreate,
Delete: resourceVcdDNATDelete,
Read: resourceVcdDNATRead,

Schema: map[string]*schema.Schema{
"edge_gateway": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"external_ip": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"port": &schema.Schema{
Type: schema.TypeInt,
Required: true,
ForceNew: true,
},

"internal_ip": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}

func resourceVcdDNATCreate(d *schema.ResourceData, meta interface{}) error {
vcdClient := meta.(*VCDClient)
// Multiple VCD components need to run operations on the Edge Gateway, as
// the edge gatway will throw back an error if it is already performing an
// operation we must wait until we can aquire a lock on the client
vcdClient.Mutex.Lock()
defer vcdClient.Mutex.Unlock()
portString := getPortString(d.Get("port").(int))

edgeGateway, err := vcdClient.OrgVdc.FindEdgeGateway(d.Get("edge_gateway").(string))

if err != nil {
return fmt.Errorf("Unable to find edge gateway: %#v", err)
}

// Creating a loop to offer further protection from the edge gateway erroring
// due to being busy eg another person is using another client so wouldn't be
// constrained by out lock. If the edge gateway reurns with a busy error, wait
// 3 seconds and then try again. Continue until a non-busy error or success

err = retryCall(vcdClient.MaxRetryTimeout, func() error {
task, err := edgeGateway.AddNATMapping("DNAT", d.Get("external_ip").(string),
d.Get("internal_ip").(string),
portString)
if err != nil {
return fmt.Errorf("Error setting DNAT rules: %#v", err)
}

return task.WaitTaskCompletion()
})

if err != nil {
return fmt.Errorf("Error completing tasks: %#v", err)
}

d.SetId(d.Get("external_ip").(string) + "_" + portString)
return nil
}

func resourceVcdDNATRead(d *schema.ResourceData, meta interface{}) error {
vcdClient := meta.(*VCDClient)
e, err := vcdClient.OrgVdc.FindEdgeGateway(d.Get("edge_gateway").(string))

if err != nil {
return fmt.Errorf("Unable to find edge gateway: %#v", err)
}

var found bool

for _, r := range e.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService.NatRule {
if r.RuleType == "DNAT" &&
r.GatewayNatRule.OriginalIP == d.Get("external_ip").(string) &&
r.GatewayNatRule.OriginalPort == getPortString(d.Get("port").(int)) {
found = true
d.Set("internal_ip", r.GatewayNatRule.TranslatedIP)
}
}

if !found {
d.SetId("")
}

return nil
}

func resourceVcdDNATDelete(d *schema.ResourceData, meta interface{}) error {
vcdClient := meta.(*VCDClient)
// Multiple VCD components need to run operations on the Edge Gateway, as
// the edge gatway will throw back an error if it is already performing an
// operation we must wait until we can aquire a lock on the client
vcdClient.Mutex.Lock()
defer vcdClient.Mutex.Unlock()
portString := getPortString(d.Get("port").(int))

edgeGateway, err := vcdClient.OrgVdc.FindEdgeGateway(d.Get("edge_gateway").(string))

if err != nil {
return fmt.Errorf("Unable to find edge gateway: %#v", err)
}
err = retryCall(vcdClient.MaxRetryTimeout, func() error {
task, err := edgeGateway.RemoveNATMapping("DNAT", d.Get("external_ip").(string),
d.Get("internal_ip").(string),
portString)
if err != nil {
return fmt.Errorf("Error setting DNAT rules: %#v", err)
}

return task.WaitTaskCompletion()
})
if err != nil {
return fmt.Errorf("Error completing tasks: %#v", err)
}
return nil
}
Loading

0 comments on commit 9e68c34

Please sign in to comment.