Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VMWare vCloud Director Support #3785

Merged
merged 27 commits into from
Dec 3, 2015
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
8780bd2
Added vCloud Director provider with tests and provider documentation
Oct 26, 2015
8376a5a
Merge branch 'terraform'
Nov 2, 2015
965882b
Added protection for api limiting
Nov 2, 2015
95ec9a9
Refresh firewall rules after each failure before trying to append new…
Nov 2, 2015
fc7dcb8
Minor change to documentation
Nov 2, 2015
cb90e6c
Merge branch 'master' into hmrc
Nov 5, 2015
7c09f96
Changed vmware-govcd dependency to pull from hmrc
Nov 6, 2015
bda4ef7
Merge branch 'terraform' into hmrc
Nov 6, 2015
b6abb91
Class a resource that is in tfstate but unable to be found on the pro…
Nov 6, 2015
a15c99e
Code cleanup to address PR comments
Nov 10, 2015
a05ff89
Changed documentation to better show what can be done with firewall r…
Nov 10, 2015
dc8924b
Changed vcd_vapp resource to make better use of Update function
Nov 10, 2015
5df9d22
Minor doc updates
nickithewatt Nov 11, 2015
ecc4ce3
Converted firewall_rules rule set to a list type. Code tidy
Nov 16, 2015
f8e1a64
Merge branch 'master' of github.com:hmrc/terraform into hmrc
Nov 16, 2015
cc54785
Merge branch 'terraform' into hmrc
Nov 17, 2015
f140c15
Fixed null pointer panic during firewall rules test
Nov 17, 2015
c8dfecc
Check where nested structs could possibly be nil before trying to acc…
Nov 17, 2015
29dfc43
Add retry calls to protect against api rate limiting
Nov 17, 2015
b0fdf8a
Fixed failing test
Nov 18, 2015
815ff7a
Merge branch 'terraform' into hmrc
Nov 18, 2015
f1c2be9
Make maxRetryTimeout (in seconds) configurable
nickithewatt Nov 18, 2015
49195f8
Added docs for maxRetryTimeout
nickithewatt Nov 21, 2015
3809315
Upped default maxRetryTimeout from 30s -> 60s
nickithewatt Nov 23, 2015
5dde514
Merge pull request #1 from hmrc/timeout
devopsbrett Nov 23, 2015
a026673
Only undeploy a machine if it is switched on
Nov 25, 2015
aec94b1
Don't error if unable to undeploy
Nov 25, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
})
}
32 changes: 32 additions & 0 deletions builtin/providers/vcd/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package vcd

import (
"fmt"
"net/url"

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

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

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

vcdclient := govcd.NewVCDClient(*u)
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
}
69 changes: 69 additions & 0 deletions builtin/providers/vcd/provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
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",
},
},

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),
}

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")
}
}
140 changes: 140 additions & 0 deletions builtin/providers/vcd/resource_vcd_dnat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package vcd

import (
"fmt"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hmrc/vmware-govcd"
"strings"
)

func resourceVcdDNAT() *schema.Resource {
return &schema.Resource{
Create: resourceVcdDNATCreate,
Update: resourceVcdDNATUpdate,
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,
},

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

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

func resourceVcdDNATCreate(d *schema.ResourceData, meta interface{}) error {
vcd_client := meta.(*govcd.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
vcd_client.Mutex.Lock()
defer vcd_client.Mutex.Unlock()
portString := getPortString(d.Get("port").(int))

edgeGateway, err := vcd_client.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(4, 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 resourceVcdDNATUpdate(d *schema.ResourceData, meta interface{}) error {
return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there's no implementation for Update it's better to just omit this function from the Resource definition. That will also trigger a check to ensure that all the fields are set to ForceNew, which they should be when an Update is not available.


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

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

idSplit := strings.Split(d.Id(), "_")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than leaning on the semantics of data being stored inside the ID, it seems more straightforward to just call d.Get("external_ip") and d.Get("port") here, don't you think?

var found bool

for _, r := range e.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService.NatRule {
if r.RuleType == "DNAT" &&
r.GatewayNatRule.OriginalIP == idSplit[0] &&
r.GatewayNatRule.OriginalPort == idSplit[1] {
found = true
d.Set("internal_ip", r.GatewayNatRule.TranslatedIP)
}
}

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

return nil
}

func resourceVcdDNATDelete(d *schema.ResourceData, meta interface{}) error {
vcd_client := meta.(*govcd.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
vcd_client.Mutex.Lock()
defer vcd_client.Mutex.Unlock()
portString := getPortString(d.Get("port").(int))

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

if err != nil {
return fmt.Errorf("Unable to find edge gateway: %#v", err)
}
err = retryCall(4, 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