Merge pull request #3785 from hmrc/master

VMWare vCloud Director Support
This commit is contained in:
Paul Hinze 2015-12-03 15:51:47 -06:00
commit 9e68c34abe
24 changed files with 2347 additions and 0 deletions

View File

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

View File

@ -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
}

View File

@ -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()
}

View File

@ -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")
}
}

View File

@ -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
}

View File

@ -0,0 +1,120 @@
package vcd
import (
"fmt"
"os"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/hmrc/vmware-govcd"
)
func TestAccVcdDNAT_Basic(t *testing.T) {
if v := os.Getenv("VCD_EXTERNAL_IP"); v == "" {
t.Skip("Environment variable VCD_EXTERNAL_IP must be set to run DNAT tests")
return
}
var e govcd.EdgeGateway
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckVcdDNATDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: fmt.Sprintf(testAccCheckVcdDnat_basic, os.Getenv("VCD_EDGE_GATWEWAY"), os.Getenv("VCD_EXTERNAL_IP")),
Check: resource.ComposeTestCheckFunc(
testAccCheckVcdDNATExists("vcd_dnat.bar", &e),
resource.TestCheckResourceAttr(
"vcd_dnat.bar", "external_ip", os.Getenv("VCD_EXTERNAL_IP")),
resource.TestCheckResourceAttr(
"vcd_dnat.bar", "port", "77"),
resource.TestCheckResourceAttr(
"vcd_dnat.bar", "internal_ip", "10.10.102.60"),
),
},
},
})
}
func testAccCheckVcdDNATExists(n string, gateway *govcd.EdgeGateway) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No DNAT ID is set")
}
conn := testAccProvider.Meta().(*VCDClient)
gatewayName := rs.Primary.Attributes["edge_gateway"]
edgeGateway, err := conn.OrgVdc.FindEdgeGateway(gatewayName)
if err != nil {
return fmt.Errorf("Could not find edge gateway")
}
var found bool
for _, v := range edgeGateway.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService.NatRule {
if v.RuleType == "DNAT" &&
v.GatewayNatRule.OriginalIP == os.Getenv("VCD_EXTERNAL_IP") &&
v.GatewayNatRule.OriginalPort == "77" &&
v.GatewayNatRule.TranslatedIP == "10.10.102.60" {
found = true
}
}
if !found {
return fmt.Errorf("DNAT rule was not found")
}
*gateway = edgeGateway
return nil
}
}
func testAccCheckVcdDNATDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*VCDClient)
for _, rs := range s.RootModule().Resources {
if rs.Type != "vcd_dnat" {
continue
}
gatewayName := rs.Primary.Attributes["edge_gateway"]
edgeGateway, err := conn.OrgVdc.FindEdgeGateway(gatewayName)
if err != nil {
return fmt.Errorf("Could not find edge gateway")
}
var found bool
for _, v := range edgeGateway.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService.NatRule {
if v.RuleType == "DNAT" &&
v.GatewayNatRule.OriginalIP == os.Getenv("VCD_EXTERNAL_IP") &&
v.GatewayNatRule.OriginalPort == "77" &&
v.GatewayNatRule.TranslatedIP == "10.10.102.60" {
found = true
}
}
if found {
return fmt.Errorf("DNAT rule still exists.")
}
}
return nil
}
const testAccCheckVcdDnat_basic = `
resource "vcd_dnat" "bar" {
edge_gateway = "%s"
external_ip = "%s"
port = 77
internal_ip = "10.10.102.60"
}
`

View File

@ -0,0 +1,197 @@
package vcd
import (
"fmt"
"github.com/hashicorp/terraform/helper/schema"
types "github.com/hmrc/vmware-govcd/types/v56"
"log"
"strings"
)
func resourceVcdFirewallRules() *schema.Resource {
return &schema.Resource{
Create: resourceVcdFirewallRulesCreate,
Delete: resourceFirewallRulesDelete,
Read: resourceFirewallRulesRead,
Schema: map[string]*schema.Schema{
"edge_gateway": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"default_action": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"rule": &schema.Schema{
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"description": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"policy": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"protocol": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"destination_port": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"destination_ip": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"source_port": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"source_ip": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
},
},
},
},
}
}
func resourceVcdFirewallRulesCreate(d *schema.ResourceData, meta interface{}) error {
vcdClient := meta.(*VCDClient)
vcdClient.Mutex.Lock()
defer vcdClient.Mutex.Unlock()
edgeGateway, err := vcdClient.OrgVdc.FindEdgeGateway(d.Get("edge_gateway").(string))
if err != nil {
return fmt.Errorf("Unable to find edge gateway: %s", err)
}
err = retryCall(vcdClient.MaxRetryTimeout, func() error {
edgeGateway.Refresh()
firewallRules, _ := expandFirewallRules(d, edgeGateway.EdgeGateway)
task, err := edgeGateway.CreateFirewallRules(d.Get("default_action").(string), firewallRules)
if err != nil {
log.Printf("[INFO] Error setting firewall rules: %s", err)
return fmt.Errorf("Error setting firewall rules: %#v", err)
}
return task.WaitTaskCompletion()
})
if err != nil {
return fmt.Errorf("Error completing tasks: %#v", err)
}
d.SetId(d.Get("edge_gateway").(string))
return resourceFirewallRulesRead(d, meta)
}
func resourceFirewallRulesDelete(d *schema.ResourceData, meta interface{}) error {
vcdClient := meta.(*VCDClient)
vcdClient.Mutex.Lock()
defer vcdClient.Mutex.Unlock()
edgeGateway, err := vcdClient.OrgVdc.FindEdgeGateway(d.Get("edge_gateway").(string))
firewallRules := deleteFirewallRules(d, edgeGateway.EdgeGateway)
defaultAction := edgeGateway.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.FirewallService.DefaultAction
task, err := edgeGateway.CreateFirewallRules(defaultAction, firewallRules)
if err != nil {
return fmt.Errorf("Error deleting firewall rules: %#v", err)
}
err = task.WaitTaskCompletion()
if err != nil {
return fmt.Errorf("Error completing tasks: %#v", err)
}
return nil
}
func resourceFirewallRulesRead(d *schema.ResourceData, meta interface{}) error {
vcdClient := meta.(*VCDClient)
edgeGateway, err := vcdClient.OrgVdc.FindEdgeGateway(d.Get("edge_gateway").(string))
if err != nil {
return fmt.Errorf("Error finding edge gateway: %#v", err)
}
ruleList := d.Get("rule").([]interface{})
firewallRules := *edgeGateway.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.FirewallService
rulesCount := d.Get("rule.#").(int)
for i := 0; i < rulesCount; i++ {
prefix := fmt.Sprintf("rule.%d", i)
if d.Get(prefix+".id").(string) == "" {
log.Printf("[INFO] Rule %d has no id. Searching...", i)
ruleid, err := matchFirewallRule(d, prefix, firewallRules.FirewallRule)
if err == nil {
currentRule := ruleList[i].(map[string]interface{})
currentRule["id"] = ruleid
ruleList[i] = currentRule
}
}
}
d.Set("rule", ruleList)
d.Set("default_action", firewallRules.DefaultAction)
return nil
}
func deleteFirewallRules(d *schema.ResourceData, gateway *types.EdgeGateway) []*types.FirewallRule {
firewallRules := gateway.Configuration.EdgeGatewayServiceConfiguration.FirewallService.FirewallRule
rulesCount := d.Get("rule.#").(int)
fwrules := make([]*types.FirewallRule, 0, len(firewallRules)-rulesCount)
for _, f := range firewallRules {
keep := true
for i := 0; i < rulesCount; i++ {
if d.Get(fmt.Sprintf("rule.%d.id", i)).(string) != f.ID {
continue
}
keep = false
}
if keep {
fwrules = append(fwrules, f)
}
}
return fwrules
}
func matchFirewallRule(d *schema.ResourceData, prefix string, rules []*types.FirewallRule) (string, error) {
for _, m := range rules {
if d.Get(prefix+".description").(string) == m.Description &&
d.Get(prefix+".policy").(string) == m.Policy &&
strings.ToLower(d.Get(prefix+".protocol").(string)) == getProtocol(*m.Protocols) &&
strings.ToLower(d.Get(prefix+".destination_port").(string)) == getPortString(m.Port) &&
strings.ToLower(d.Get(prefix+".destination_ip").(string)) == strings.ToLower(m.DestinationIP) &&
strings.ToLower(d.Get(prefix+".source_port").(string)) == getPortString(m.SourcePort) &&
strings.ToLower(d.Get(prefix+".source_ip").(string)) == strings.ToLower(m.SourceIP) {
return m.ID, nil
}
}
return "", fmt.Errorf("Unable to find rule")
}

View File

@ -0,0 +1,108 @@
package vcd
import (
"fmt"
"log"
"os"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/hmrc/vmware-govcd"
)
func TestAccVcdFirewallRules_basic(t *testing.T) {
var existingRules, fwRules govcd.EdgeGateway
newConfig := createFirewallRulesConfigs(&existingRules)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: newConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckVcdFirewallRulesExists("vcd_firewall_rules.bar", &fwRules),
testAccCheckVcdFirewallRulesAttributes(&fwRules, &existingRules),
),
},
},
})
}
func testAccCheckVcdFirewallRulesExists(n string, gateway *govcd.EdgeGateway) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No Record ID is set")
}
conn := testAccProvider.Meta().(*VCDClient)
resp, err := conn.OrgVdc.FindEdgeGateway(rs.Primary.ID)
if err != nil {
return fmt.Errorf("Edge Gateway does not exist.")
}
*gateway = resp
return nil
}
}
func testAccCheckVcdFirewallRulesAttributes(newRules, existingRules *govcd.EdgeGateway) resource.TestCheckFunc {
return func(s *terraform.State) error {
if len(newRules.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.FirewallService.FirewallRule) != len(existingRules.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.FirewallService.FirewallRule)+1 {
return fmt.Errorf("New firewall rule not added: %d != %d",
len(newRules.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.FirewallService.FirewallRule),
len(existingRules.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.FirewallService.FirewallRule)+1)
}
return nil
}
}
func createFirewallRulesConfigs(existingRules *govcd.EdgeGateway) string {
config := Config{
User: os.Getenv("VCD_USER"),
Password: os.Getenv("VCD_PASSWORD"),
Org: os.Getenv("VCD_ORG"),
Href: os.Getenv("VCD_URL"),
VDC: os.Getenv("VCD_VDC"),
MaxRetryTimeout: 240,
}
conn, err := config.Client()
if err != nil {
return fmt.Sprintf(testAccCheckVcdFirewallRules_add, "", "")
}
edgeGateway, _ := conn.OrgVdc.FindEdgeGateway(os.Getenv("VCD_EDGE_GATWEWAY"))
*existingRules = edgeGateway
log.Printf("[DEBUG] Edge gateway: %#v", edgeGateway)
firewallRules := *edgeGateway.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.FirewallService
return fmt.Sprintf(testAccCheckVcdFirewallRules_add, os.Getenv("VCD_EDGE_GATEWAY"), firewallRules.DefaultAction)
}
const testAccCheckVcdFirewallRules_add = `
resource "vcd_firewall_rules" "bar" {
edge_gateway = "%s"
default_action = "%s"
rule {
description = "Test rule"
policy = "allow"
protocol = "any"
destination_port = "any"
destination_ip = "any"
source_port = "any"
source_ip = "any"
}
}
`

View File

@ -0,0 +1,264 @@
package vcd
import (
"log"
"bytes"
"fmt"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/schema"
types "github.com/hmrc/vmware-govcd/types/v56"
"strings"
)
func resourceVcdNetwork() *schema.Resource {
return &schema.Resource{
Create: resourceVcdNetworkCreate,
Read: resourceVcdNetworkRead,
Delete: resourceVcdNetworkDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"fence_mode": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Default: "natRouted",
},
"edge_gateway": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"netmask": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Default: "255.255.255.0",
},
"gateway": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"dns1": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Default: "8.8.8.8",
},
"dns2": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Default: "8.8.4.4",
},
"dns_suffix": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"href": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"dhcp_pool": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"start_address": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"end_address": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
},
},
Set: resourceVcdNetworkIPAddressHash,
},
"static_ip_pool": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"start_address": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"end_address": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
},
},
Set: resourceVcdNetworkIPAddressHash,
},
},
}
}
func resourceVcdNetworkCreate(d *schema.ResourceData, meta interface{}) error {
vcdClient := meta.(*VCDClient)
log.Printf("[TRACE] CLIENT: %#v", vcdClient)
vcdClient.Mutex.Lock()
defer vcdClient.Mutex.Unlock()
edgeGateway, err := vcdClient.OrgVdc.FindEdgeGateway(d.Get("edge_gateway").(string))
ipRanges := expandIPRange(d.Get("static_ip_pool").(*schema.Set).List())
newnetwork := &types.OrgVDCNetwork{
Xmlns: "http://www.vmware.com/vcloud/v1.5",
Name: d.Get("name").(string),
Configuration: &types.NetworkConfiguration{
FenceMode: d.Get("fence_mode").(string),
IPScopes: &types.IPScopes{
IPScope: types.IPScope{
IsInherited: false,
Gateway: d.Get("gateway").(string),
Netmask: d.Get("netmask").(string),
DNS1: d.Get("dns1").(string),
DNS2: d.Get("dns2").(string),
DNSSuffix: d.Get("dns_suffix").(string),
IPRanges: &ipRanges,
},
},
BackwardCompatibilityMode: true,
},
EdgeGateway: &types.Reference{
HREF: edgeGateway.EdgeGateway.HREF,
},
IsShared: false,
}
log.Printf("[INFO] NETWORK: %#v", newnetwork)
err = retryCall(vcdClient.MaxRetryTimeout, func() error {
return vcdClient.OrgVdc.CreateOrgVDCNetwork(newnetwork)
})
if err != nil {
return fmt.Errorf("Error: %#v", err)
}
err = vcdClient.OrgVdc.Refresh()
if err != nil {
return fmt.Errorf("Error refreshing vdc: %#v", err)
}
network, err := vcdClient.OrgVdc.FindVDCNetwork(d.Get("name").(string))
if err != nil {
return fmt.Errorf("Error finding network: %#v", err)
}
if dhcp, ok := d.GetOk("dhcp_pool"); ok {
err = retryCall(vcdClient.MaxRetryTimeout, func() error {
task, err := edgeGateway.AddDhcpPool(network.OrgVDCNetwork, dhcp.(*schema.Set).List())
if err != nil {
return fmt.Errorf("Error adding DHCP pool: %#v", err)
}
return task.WaitTaskCompletion()
})
if err != nil {
return fmt.Errorf("Error completing tasks: %#v", err)
}
}
d.SetId(d.Get("name").(string))
return resourceVcdNetworkRead(d, meta)
}
func resourceVcdNetworkRead(d *schema.ResourceData, meta interface{}) error {
vcdClient := meta.(*VCDClient)
log.Printf("[DEBUG] VCD Client configuration: %#v", vcdClient)
log.Printf("[DEBUG] VCD Client configuration: %#v", vcdClient.OrgVdc)
err := vcdClient.OrgVdc.Refresh()
if err != nil {
return fmt.Errorf("Error refreshing vdc: %#v", err)
}
network, err := vcdClient.OrgVdc.FindVDCNetwork(d.Id())
if err != nil {
log.Printf("[DEBUG] Network no longer exists. Removing from tfstate")
d.SetId("")
return nil
}
d.Set("name", network.OrgVDCNetwork.Name)
d.Set("href", network.OrgVDCNetwork.HREF)
if c := network.OrgVDCNetwork.Configuration; c != nil {
d.Set("fence_mode", c.FenceMode)
if c.IPScopes != nil {
d.Set("gateway", c.IPScopes.IPScope.Gateway)
d.Set("netmask", c.IPScopes.IPScope.Netmask)
d.Set("dns1", c.IPScopes.IPScope.DNS1)
d.Set("dns2", c.IPScopes.IPScope.DNS2)
}
}
return nil
}
func resourceVcdNetworkDelete(d *schema.ResourceData, meta interface{}) error {
vcdClient := meta.(*VCDClient)
vcdClient.Mutex.Lock()
defer vcdClient.Mutex.Unlock()
err := vcdClient.OrgVdc.Refresh()
if err != nil {
return fmt.Errorf("Error refreshing vdc: %#v", err)
}
network, err := vcdClient.OrgVdc.FindVDCNetwork(d.Id())
if err != nil {
return fmt.Errorf("Error finding network: %#v", err)
}
err = retryCall(vcdClient.MaxRetryTimeout, func() error {
task, err := network.Delete()
if err != nil {
return fmt.Errorf("Error Deleting Network: %#v", err)
}
return task.WaitTaskCompletion()
})
if err != nil {
return err
}
return nil
}
func resourceVcdNetworkIPAddressHash(v interface{}) int {
var buf bytes.Buffer
m := v.(map[string]interface{})
buf.WriteString(fmt.Sprintf("%s-",
strings.ToLower(m["start_address"].(string))))
buf.WriteString(fmt.Sprintf("%s-",
strings.ToLower(m["end_address"].(string))))
return hashcode.String(buf.String())
}

View File

@ -0,0 +1,107 @@
package vcd
import (
"fmt"
"os"
"regexp"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/hmrc/vmware-govcd"
)
func TestAccVcdNetwork_Basic(t *testing.T) {
var network govcd.OrgVDCNetwork
generatedHrefRegexp := regexp.MustCompile("^https://")
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckVcdNetworkDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: fmt.Sprintf(testAccCheckVcdNetwork_basic, os.Getenv("VCD_EDGE_GATWEWAY")),
Check: resource.ComposeTestCheckFunc(
testAccCheckVcdNetworkExists("vcd_network.foonet", &network),
testAccCheckVcdNetworkAttributes(&network),
resource.TestCheckResourceAttr(
"vcd_network.foonet", "name", "foonet"),
resource.TestCheckResourceAttr(
"vcd_network.foonet", "static_ip_pool.#", "1"),
resource.TestCheckResourceAttr(
"vcd_network.foonet", "gateway", "10.10.102.1"),
resource.TestMatchResourceAttr(
"vcd_network.foonet", "href", generatedHrefRegexp),
),
},
},
})
}
func testAccCheckVcdNetworkExists(n string, network *govcd.OrgVDCNetwork) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No VAPP ID is set")
}
conn := testAccProvider.Meta().(*VCDClient)
resp, err := conn.OrgVdc.FindVDCNetwork(rs.Primary.ID)
if err != nil {
return fmt.Errorf("Network does not exist.")
}
*network = resp
return nil
}
}
func testAccCheckVcdNetworkDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*VCDClient)
for _, rs := range s.RootModule().Resources {
if rs.Type != "vcd_network" {
continue
}
_, err := conn.OrgVdc.FindVDCNetwork(rs.Primary.ID)
if err == nil {
return fmt.Errorf("Network still exists.")
}
return nil
}
return nil
}
func testAccCheckVcdNetworkAttributes(network *govcd.OrgVDCNetwork) resource.TestCheckFunc {
return func(s *terraform.State) error {
if network.OrgVDCNetwork.Name != "foonet" {
return fmt.Errorf("Bad name: %s", network.OrgVDCNetwork.Name)
}
return nil
}
}
const testAccCheckVcdNetwork_basic = `
resource "vcd_network" "foonet" {
name = "foonet"
edge_gateway = "%s"
gateway = "10.10.102.1"
static_ip_pool {
start_address = "10.10.102.2"
end_address = "10.10.102.254"
}
}
`

View File

@ -0,0 +1,122 @@
package vcd
import (
"fmt"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceVcdSNAT() *schema.Resource {
return &schema.Resource{
Create: resourceVcdSNATCreate,
Delete: resourceVcdSNATDelete,
Read: resourceVcdSNATRead,
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,
},
"internal_ip": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}
func resourceVcdSNATCreate(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()
// 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
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.AddNATMapping("SNAT", d.Get("internal_ip").(string),
d.Get("external_ip").(string),
"any")
if err != nil {
return fmt.Errorf("Error setting SNAT rules: %#v", err)
}
return task.WaitTaskCompletion()
})
if err != nil {
return err
}
d.SetId(d.Get("internal_ip").(string))
return nil
}
func resourceVcdSNATRead(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 == "SNAT" &&
r.GatewayNatRule.OriginalIP == d.Id() {
found = true
d.Set("external_ip", r.GatewayNatRule.TranslatedIP)
}
}
if !found {
d.SetId("")
}
return nil
}
func resourceVcdSNATDelete(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()
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("SNAT", d.Get("internal_ip").(string),
d.Get("external_ip").(string),
"")
if err != nil {
return fmt.Errorf("Error setting SNAT rules: %#v", err)
}
return task.WaitTaskCompletion()
})
if err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,119 @@
package vcd
import (
"fmt"
"os"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/hmrc/vmware-govcd"
)
func TestAccVcdSNAT_Basic(t *testing.T) {
if v := os.Getenv("VCD_EXTERNAL_IP"); v == "" {
t.Skip("Environment variable VCD_EXTERNAL_IP must be set to run SNAT tests")
return
}
var e govcd.EdgeGateway
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckVcdSNATDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: fmt.Sprintf(testAccCheckVcdSnat_basic, os.Getenv("VCD_EDGE_GATWEWAY"), os.Getenv("VCD_EXTERNAL_IP")),
Check: resource.ComposeTestCheckFunc(
testAccCheckVcdSNATExists("vcd_snat.bar", &e),
resource.TestCheckResourceAttr(
"vcd_snat.bar", "external_ip", os.Getenv("VCD_EXTERNAL_IP")),
resource.TestCheckResourceAttr(
"vcd_snat.bar", "internal_ip", "10.10.102.0/24"),
),
},
},
})
}
func testAccCheckVcdSNATExists(n string, gateway *govcd.EdgeGateway) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
//return fmt.Errorf("Check this: %#v", rs.Primary)
if rs.Primary.ID == "" {
return fmt.Errorf("No SNAT ID is set")
}
conn := testAccProvider.Meta().(*VCDClient)
gatewayName := rs.Primary.Attributes["edge_gateway"]
edgeGateway, err := conn.OrgVdc.FindEdgeGateway(gatewayName)
if err != nil {
return fmt.Errorf("Could not find edge gateway")
}
var found bool
for _, v := range edgeGateway.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService.NatRule {
if v.RuleType == "SNAT" &&
v.GatewayNatRule.OriginalIP == "10.10.102.0/24" &&
v.GatewayNatRule.OriginalPort == "" &&
v.GatewayNatRule.TranslatedIP == os.Getenv("VCD_EXTERNAL_IP") {
found = true
}
}
if !found {
return fmt.Errorf("SNAT rule was not found")
}
*gateway = edgeGateway
return nil
}
}
func testAccCheckVcdSNATDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*VCDClient)
for _, rs := range s.RootModule().Resources {
if rs.Type != "vcd_snat" {
continue
}
gatewayName := rs.Primary.Attributes["edge_gateway"]
edgeGateway, err := conn.OrgVdc.FindEdgeGateway(gatewayName)
if err != nil {
return fmt.Errorf("Could not find edge gateway")
}
var found bool
for _, v := range edgeGateway.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService.NatRule {
if v.RuleType == "SNAT" &&
v.GatewayNatRule.OriginalIP == "10.10.102.0/24" &&
v.GatewayNatRule.OriginalPort == "" &&
v.GatewayNatRule.TranslatedIP == os.Getenv("VCD_EXTERNAL_IP") {
found = true
}
}
if found {
return fmt.Errorf("SNAT rule still exists.")
}
}
return nil
}
const testAccCheckVcdSnat_basic = `
resource "vcd_snat" "bar" {
edge_gateway = "%s"
external_ip = "%s"
internal_ip = "10.10.102.0/24"
}
`

View File

@ -0,0 +1,341 @@
package vcd
import (
"fmt"
"github.com/hashicorp/terraform/helper/schema"
types "github.com/hmrc/vmware-govcd/types/v56"
"log"
)
func resourceVcdVApp() *schema.Resource {
return &schema.Resource{
Create: resourceVcdVAppCreate,
Update: resourceVcdVAppUpdate,
Read: resourceVcdVAppRead,
Delete: resourceVcdVAppDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"template_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"catalog_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"network_href": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"network_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"memory": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
},
"cpus": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
},
"ip": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"initscript": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"metadata": &schema.Schema{
Type: schema.TypeMap,
Optional: true,
},
"href": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"power_on": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: true,
},
},
}
}
func resourceVcdVAppCreate(d *schema.ResourceData, meta interface{}) error {
vcdClient := meta.(*VCDClient)
catalog, err := vcdClient.Org.FindCatalog(d.Get("catalog_name").(string))
if err != nil {
return fmt.Errorf("Error finding catalog: %#v", err)
}
catalogitem, err := catalog.FindCatalogItem(d.Get("template_name").(string))
if err != nil {
return fmt.Errorf("Error finding catelog item: %#v", err)
}
vapptemplate, err := catalogitem.GetVAppTemplate()
if err != nil {
return fmt.Errorf("Error finding VAppTemplate: %#v", err)
}
log.Printf("[DEBUG] VAppTemplate: %#v", vapptemplate)
var networkHref string
net, err := vcdClient.OrgVdc.FindVDCNetwork(d.Get("network_name").(string))
if err != nil {
return fmt.Errorf("Error finding OrgVCD Network: %#v", err)
}
if attr, ok := d.GetOk("network_href"); ok {
networkHref = attr.(string)
} else {
networkHref = net.OrgVDCNetwork.HREF
}
// vapptemplate := govcd.NewVAppTemplate(&vcdClient.Client)
//
createvapp := &types.InstantiateVAppTemplateParams{
Ovf: "http://schemas.dmtf.org/ovf/envelope/1",
Xmlns: "http://www.vmware.com/vcloud/v1.5",
Name: d.Get("name").(string),
InstantiationParams: &types.InstantiationParams{
NetworkConfigSection: &types.NetworkConfigSection{
Info: "Configuration parameters for logical networks",
NetworkConfig: &types.VAppNetworkConfiguration{
NetworkName: d.Get("network_name").(string),
Configuration: &types.NetworkConfiguration{
ParentNetwork: &types.Reference{
HREF: networkHref,
},
FenceMode: "bridged",
},
},
},
},
Source: &types.Reference{
HREF: vapptemplate.VAppTemplate.HREF,
},
}
err = retryCall(vcdClient.MaxRetryTimeout, func() error {
e := vcdClient.OrgVdc.InstantiateVAppTemplate(createvapp)
if e != nil {
return fmt.Errorf("Error: %#v", e)
}
e = vcdClient.OrgVdc.Refresh()
if e != nil {
return fmt.Errorf("Error: %#v", e)
}
return nil
})
if err != nil {
return err
}
vapp, err := vcdClient.OrgVdc.FindVAppByName(d.Get("name").(string))
err = retryCall(vcdClient.MaxRetryTimeout, func() error {
task, err := vapp.ChangeVMName(d.Get("name").(string))
if err != nil {
return fmt.Errorf("Error with vm name change: %#v", err)
}
return task.WaitTaskCompletion()
})
if err != nil {
return fmt.Errorf("Error changing vmname: %#v", err)
}
err = retryCall(vcdClient.MaxRetryTimeout, func() error {
task, err := vapp.ChangeNetworkConfig(d.Get("network_name").(string), d.Get("ip").(string))
if err != nil {
return fmt.Errorf("Error with Networking change: %#v", err)
}
return task.WaitTaskCompletion()
})
if err != nil {
return fmt.Errorf("Error changing network: %#v", err)
}
if initscript, ok := d.GetOk("initscript"); ok {
err = retryCall(vcdClient.MaxRetryTimeout, func() error {
task, err := vapp.RunCustomizationScript(d.Get("name").(string), initscript.(string))
if err != nil {
return fmt.Errorf("Error with setting init script: %#v", err)
}
return task.WaitTaskCompletion()
})
if err != nil {
return fmt.Errorf("Error completing tasks: %#v", err)
}
}
d.SetId(d.Get("name").(string))
return resourceVcdVAppUpdate(d, meta)
}
func resourceVcdVAppUpdate(d *schema.ResourceData, meta interface{}) error {
vcdClient := meta.(*VCDClient)
vapp, err := vcdClient.OrgVdc.FindVAppByName(d.Id())
if err != nil {
return fmt.Errorf("Error finding VApp: %#v", err)
}
status, err := vapp.GetStatus()
if err != nil {
return fmt.Errorf("Error getting VApp status: %#v", err)
}
if d.HasChange("metadata") {
oraw, nraw := d.GetChange("metadata")
metadata := oraw.(map[string]interface{})
for k := range metadata {
task, err := vapp.DeleteMetadata(k)
if err != nil {
return fmt.Errorf("Error deleting metadata: %#v", err)
}
err = task.WaitTaskCompletion()
if err != nil {
return fmt.Errorf("Error completing tasks: %#v", err)
}
}
metadata = nraw.(map[string]interface{})
for k, v := range metadata {
task, err := vapp.AddMetadata(k, v.(string))
if err != nil {
return fmt.Errorf("Error adding metadata: %#v", err)
}
err = task.WaitTaskCompletion()
if err != nil {
return fmt.Errorf("Error completing tasks: %#v", err)
}
}
}
if d.HasChange("memory") || d.HasChange("cpus") || d.HasChange("power_on") {
if status != "POWERED_OFF" {
task, err := vapp.PowerOff()
if err != nil {
return fmt.Errorf("Error Powering Off: %#v", err)
}
err = task.WaitTaskCompletion()
if err != nil {
return fmt.Errorf("Error completing tasks: %#v", err)
}
}
if d.HasChange("memory") {
err = retryCall(vcdClient.MaxRetryTimeout, func() error {
task, err := vapp.ChangeMemorySize(d.Get("memory").(int))
if err != nil {
return fmt.Errorf("Error changing memory size: %#v", err)
}
return task.WaitTaskCompletion()
})
if err != nil {
return err
}
}
if d.HasChange("cpus") {
err = retryCall(vcdClient.MaxRetryTimeout, func() error {
task, err := vapp.ChangeCPUcount(d.Get("cpus").(int))
if err != nil {
return fmt.Errorf("Error changing cpu count: %#v", err)
}
return task.WaitTaskCompletion()
})
if err != nil {
return fmt.Errorf("Error completing task: %#v", err)
}
}
if d.Get("power_on").(bool) {
task, err := vapp.PowerOn()
if err != nil {
return fmt.Errorf("Error Powering Up: %#v", err)
}
err = task.WaitTaskCompletion()
if err != nil {
return fmt.Errorf("Error completing tasks: %#v", err)
}
}
}
return resourceVcdVAppRead(d, meta)
}
func resourceVcdVAppRead(d *schema.ResourceData, meta interface{}) error {
vcdClient := meta.(*VCDClient)
err := vcdClient.OrgVdc.Refresh()
if err != nil {
return fmt.Errorf("Error refreshing vdc: %#v", err)
}
vapp, err := vcdClient.OrgVdc.FindVAppByName(d.Id())
if err != nil {
log.Printf("[DEBUG] Unable to find vapp. Removing from tfstate")
d.SetId("")
return nil
}
d.Set("ip", vapp.VApp.Children.VM[0].NetworkConnectionSection.NetworkConnection.IPAddress)
return nil
}
func resourceVcdVAppDelete(d *schema.ResourceData, meta interface{}) error {
vcdClient := meta.(*VCDClient)
vapp, err := vcdClient.OrgVdc.FindVAppByName(d.Id())
if err != nil {
return fmt.Errorf("error finding vapp: %s", err)
}
if err != nil {
return fmt.Errorf("Error getting VApp status: %#v", err)
}
_ = retryCall(vcdClient.MaxRetryTimeout, func() error {
task, err := vapp.Undeploy()
if err != nil {
return fmt.Errorf("Error undeploying: %#v", err)
}
return task.WaitTaskCompletion()
})
err = retryCall(vcdClient.MaxRetryTimeout, func() error {
task, err := vapp.Delete()
if err != nil {
return fmt.Errorf("Error deleting: %#v", err)
}
return task.WaitTaskCompletion()
})
return err
}

View File

@ -0,0 +1,180 @@
package vcd
import (
"fmt"
"os"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/hmrc/vmware-govcd"
)
func TestAccVcdVApp_PowerOff(t *testing.T) {
var vapp govcd.VApp
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckVcdVAppDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: fmt.Sprintf(testAccCheckVcdVApp_basic, os.Getenv("VCD_EDGE_GATWEWAY")),
Check: resource.ComposeTestCheckFunc(
testAccCheckVcdVAppExists("vcd_vapp.foobar", &vapp),
testAccCheckVcdVAppAttributes(&vapp),
resource.TestCheckResourceAttr(
"vcd_vapp.foobar", "name", "foobar"),
resource.TestCheckResourceAttr(
"vcd_vapp.foobar", "ip", "10.10.102.160"),
resource.TestCheckResourceAttr(
"vcd_vapp.foobar", "power_on", "true"),
),
},
resource.TestStep{
Config: fmt.Sprintf(testAccCheckVcdVApp_powerOff, os.Getenv("VCD_EDGE_GATWEWAY")),
Check: resource.ComposeTestCheckFunc(
testAccCheckVcdVAppExists("vcd_vapp.foobar", &vapp),
testAccCheckVcdVAppAttributes_off(&vapp),
resource.TestCheckResourceAttr(
"vcd_vapp.foobar", "name", "foobar"),
resource.TestCheckResourceAttr(
"vcd_vapp.foobar", "ip", "10.10.102.160"),
resource.TestCheckResourceAttr(
"vcd_vapp.foobar", "power_on", "false"),
),
},
},
})
}
func testAccCheckVcdVAppExists(n string, vapp *govcd.VApp) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No VAPP ID is set")
}
conn := testAccProvider.Meta().(*VCDClient)
resp, err := conn.OrgVdc.FindVAppByName(rs.Primary.ID)
if err != nil {
return err
}
*vapp = resp
return nil
}
}
func testAccCheckVcdVAppDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*VCDClient)
for _, rs := range s.RootModule().Resources {
if rs.Type != "vcd_vapp" {
continue
}
_, err := conn.OrgVdc.FindVAppByName(rs.Primary.ID)
if err == nil {
return fmt.Errorf("VPCs still exist")
}
return nil
}
return nil
}
func testAccCheckVcdVAppAttributes(vapp *govcd.VApp) resource.TestCheckFunc {
return func(s *terraform.State) error {
if vapp.VApp.Name != "foobar" {
return fmt.Errorf("Bad name: %s", vapp.VApp.Name)
}
if vapp.VApp.Name != vapp.VApp.Children.VM[0].Name {
return fmt.Errorf("VApp and VM names do not match. %s != %s",
vapp.VApp.Name, vapp.VApp.Children.VM[0].Name)
}
status, _ := vapp.GetStatus()
if status != "POWERED_ON" {
return fmt.Errorf("VApp is not powered on")
}
return nil
}
}
func testAccCheckVcdVAppAttributes_off(vapp *govcd.VApp) resource.TestCheckFunc {
return func(s *terraform.State) error {
if vapp.VApp.Name != "foobar" {
return fmt.Errorf("Bad name: %s", vapp.VApp.Name)
}
if vapp.VApp.Name != vapp.VApp.Children.VM[0].Name {
return fmt.Errorf("VApp and VM names do not match. %s != %s",
vapp.VApp.Name, vapp.VApp.Children.VM[0].Name)
}
status, _ := vapp.GetStatus()
if status != "POWERED_OFF" {
return fmt.Errorf("VApp is still powered on")
}
return nil
}
}
const testAccCheckVcdVApp_basic = `
resource "vcd_network" "foonet" {
name = "foonet"
edge_gateway = "%s"
gateway = "10.10.102.1"
static_ip_pool {
start_address = "10.10.102.2"
end_address = "10.10.102.254"
}
}
resource "vcd_vapp" "foobar" {
name = "foobar"
template_name = "base-centos-7.0-x86_64_v-0.1_b-74"
catalog_name = "NubesLab"
network_name = "${vcd_network.foonet.name}"
memory = 1024
cpus = 1
ip = "10.10.102.160"
}
`
const testAccCheckVcdVApp_powerOff = `
resource "vcd_network" "foonet" {
name = "foonet"
edge_gateway = "%s"
gateway = "10.10.102.1"
static_ip_pool {
start_address = "10.10.102.2"
end_address = "10.10.102.254"
}
}
resource "vcd_vapp" "foobar" {
name = "foobar"
template_name = "base-centos-7.0-x86_64_v-0.1_b-74"
catalog_name = "NubesLab"
network_name = "${vcd_network.foonet.name}"
memory = 1024
cpus = 1
ip = "10.10.102.160"
power_on = false
}
`

View File

@ -0,0 +1,112 @@
package vcd
import (
"fmt"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
types "github.com/hmrc/vmware-govcd/types/v56"
"strconv"
"time"
)
func expandIPRange(configured []interface{}) types.IPRanges {
ipRange := make([]*types.IPRange, 0, len(configured))
for _, ipRaw := range configured {
data := ipRaw.(map[string]interface{})
ip := types.IPRange{
StartAddress: data["start_address"].(string),
EndAddress: data["end_address"].(string),
}
ipRange = append(ipRange, &ip)
}
ipRanges := types.IPRanges{
IPRange: ipRange,
}
return ipRanges
}
func expandFirewallRules(d *schema.ResourceData, gateway *types.EdgeGateway) ([]*types.FirewallRule, error) {
//firewallRules := make([]*types.FirewallRule, 0, len(configured))
firewallRules := gateway.Configuration.EdgeGatewayServiceConfiguration.FirewallService.FirewallRule
rulesCount := d.Get("rule.#").(int)
for i := 0; i < rulesCount; i++ {
prefix := fmt.Sprintf("rule.%d", i)
var protocol *types.FirewallRuleProtocols
switch d.Get(prefix + ".protocol").(string) {
case "tcp":
protocol = &types.FirewallRuleProtocols{
TCP: true,
}
case "udp":
protocol = &types.FirewallRuleProtocols{
UDP: true,
}
case "icmp":
protocol = &types.FirewallRuleProtocols{
ICMP: true,
}
default:
protocol = &types.FirewallRuleProtocols{
Any: true,
}
}
rule := &types.FirewallRule{
//ID: strconv.Itoa(len(configured) - i),
IsEnabled: true,
MatchOnTranslate: false,
Description: d.Get(prefix + ".description").(string),
Policy: d.Get(prefix + ".policy").(string),
Protocols: protocol,
Port: getNumericPort(d.Get(prefix + ".destination_port")),
DestinationPortRange: d.Get(prefix + ".destination_port").(string),
DestinationIP: d.Get(prefix + ".destination_ip").(string),
SourcePort: getNumericPort(d.Get(prefix + ".source_port")),
SourcePortRange: d.Get(prefix + ".source_port").(string),
SourceIP: d.Get(prefix + ".source_ip").(string),
EnableLogging: false,
}
firewallRules = append(firewallRules, rule)
}
return firewallRules, nil
}
func getProtocol(protocol types.FirewallRuleProtocols) string {
if protocol.TCP {
return "tcp"
}
if protocol.UDP {
return "udp"
}
if protocol.ICMP {
return "icmp"
}
return "any"
}
func getNumericPort(portrange interface{}) int {
i, err := strconv.Atoi(portrange.(string))
if err != nil {
return -1
}
return i
}
func getPortString(port int) string {
if port == -1 {
return "any"
}
portstring := strconv.Itoa(port)
return portstring
}
func retryCall(seconds int, f resource.RetryFunc) error {
return resource.Retry(time.Duration(seconds)*time.Second, f)
}

View File

@ -25,6 +25,7 @@ body.layout-rundeck,
body.layout-statuscake,
body.layout-template,
body.layout-tls,
body.layout-vcd,
body.layout-vsphere,
body.layout-docs,
body.layout-downloads,

View File

@ -0,0 +1,58 @@
---
layout: "vcd"
page_title: "Provider: VMware vCloudDirector"
sidebar_current: "docs-vcd-index"
description: |-
The VMware vCloud Director provider is used to interact with the resources supported by VMware vCloud Director. The provider needs to be configured with the proper credentials before it can be used.
---
# VMware vCloud Director Provider
The VMware vCloud Director provider is used to interact with the resources supported by VMware vCloud Director. The provider needs to be configured with the proper credentials before it can be used.
Use the navigation to the left to read about the available resources.
~> **NOTE:** The VMware vCloud Director Provider currently represents _initial support_ and therefore may undergo significant changes as the community improves it.
## Example Usage
```
# Configure the VMware vCloud Director Provider
provider "vcd" {
user = "${var.vcd_user}"
password = "${var.vcd_pass}"
org = "${var.vcd_org}"
url = "${var.vcd_url}"
vdc = "${var.vcd_vdc}"
maxRetryTimeout = "${var.vcd_maxRetryTimeout}"
}
# Create a new network
resource "vcd_network" "net" {
...
}
```
## Argument Reference
The following arguments are used to configure the VMware vCloud Director Provider:
* `user` - (Required) This is the username for vCloud Director API operations. Can also
be specified with the `VCD_USER` environment variable.
* `password` - (Required) This is the password for vCloud Director API operations. Can
also be specified with the `VCD_PASSWORD` environment variable.
* `org` - (Required) This is the vCloud Director Org on which to run API
operations. Can also be specified with the `VCD_ORG` environment
variable.
* `url` - (Required) This is the URL for the vCloud Director API.
Can also be specified with the `VCD_URL` environment variable.
* `vdc` - (Optional) This is the virtual datacenter within vCloud Director to run
API operations against. If not set the plugin will select the first virtual
datacenter available to your Org. Can also be specified with the `VCD_VDC` environment
variable.
* `maxRetryTimeout` - (Optional) This provides you with the ability to specify the maximum
amount of time (in seconds) you are prepared to wait for interactions on resources managed
by vCloud Director to be successful. If a resource action fails, the action will be retried
(as long as it is still within the `maxRetryTimeout` value) to try and ensure success.
Defaults to 60 seconds if not set.
Can also be specified with the `VCD_MAX_RETRY_TIMEOUT` environment variable.

View File

@ -0,0 +1,32 @@
---
layout: "vcd"
page_title: "vCloudDirector: vcd_dnat"
sidebar_current: "docs-vcd-resource-dnat"
description: |-
Provides a vCloud Director DNAT resource. This can be used to create, modify, and delete destination NATs to map external IPs to a VM.
---
# vcd\_dnat
Provides a vCloud Director DNAT resource. This can be used to create, modify,
and delete destination NATs to map an external IP/port to a VM.
## Example Usage
```
resource "vcd_dnat" "web" {
edge_gateway = "Edge Gateway Name"
external_ip = "78.101.10.20"
port = 80
internal_ip = "10.10.0.5"
}
```
## Argument Reference
The following arguments are supported:
* `edge_gateway` - (Required) The name of the edge gateway on which to apply the DNAT
* `external_ip` - (Required) One of the external IPs available on your Edge Gateway
* `port` - (Required) The port number to map
* `internal_ip` - (Required) The IP of the VM to map to

View File

@ -0,0 +1,83 @@
---
layout: "vcd"
page_title: "vCloudDirector: vcd_firewall_rules"
sidebar_current: "docs-vcd-resource-firewall-rules"
description: |-
Provides a vCloud Director Firewall resource. This can be used to create, modify, and delete firewall settings and rules.
---
# vcd\_firewall\_rules
Provides a vCloud Director Firewall resource. This can be used to create,
modify, and delete firewall settings and rules.
## Example Usage
```
resource "vcd_firewall_rules" "fw" {
edge_gateway = "Edge Gateway Name"
default_action = "drop"
rule {
description = "deny-ftp-out"
policy = "deny"
protocol = "tcp"
destination_port = "21"
destination_ip = "any"
source_port = "any"
source_ip = "10.10.0.0/24"
}
rule {
description = "allow-outbound"
policy = "allow"
protocol = "any"
destination_port = "any"
destination_ip = "any"
source_port = "any"
source_ip = "10.10.0.0/24"
}
}
resource "vcd_vapp" "web" {
...
}
resource "vcd_firewall_rules" "fw-web" {
edge_gateway = "Edge Gateway Name"
default_action = "drop"
rule {
description = "allow-web"
policy = "allow"
protocol = "tcp"
destination_port = "80"
destination_ip = "${vcd_vapp.web.ip}"
source_port = "any"
source_ip = "any"
}
}
```
## Argument Reference
The following arguments are supported:
* `edge_gateway` - (Required) The name of the edge gateway on which to apply the Firewall Rules
* `default_action` - (Required) Either "allow" or "deny". Specifies what to do should none of the rules match
* `rule` - (Optional) Configures a firewall rule; see [Rules](#rules) below for details.
<a id="rules"></a>
## Rules
Each firewall rule supports the following attributes:
* `description` - (Required) Description of the fireall rule
* `policy` - (Required) Specifies what to do when this rule is matched. Either "allow" or "deny"
* `protocol` - (Required) The protocol to match. One of "tcp", "udp", "icmp" or "any"
* `destination_port` - (Required) The destination port to match. Either a port number or "any"
* `destination_ip` - (Required) The destination IP to match. Either an IP address, IP range or "any"
* `source_port` - (Required) The source port to match. Either a port number or "any"
* `source_ip` - (Required) The source IP to match. Either an IP address, IP range or "any"

View File

@ -0,0 +1,57 @@
---
layout: "vcd"
page_title: "vCloudDirector: vcd_network"
sidebar_current: "docs-vcd-resource-network"
description: |-
Provides a vCloud Director VDC Network. This can be used to create, modify, and delete internal networks for vApps to connect.
---
# vcd\_network
Provides a vCloud Director VDC Network. This can be used to create,
modify, and delete internal networks for vApps to connect.
## Example Usage
```
resource "vcd_network" "net" {
name = "my-net"
edge_gateway = "Edge Gateway Name"
gateway = "10.10.0.1"
dhcp_pool {
start_address = "10.10.0.2"
end_address = "10.10.0.100"
}
static_ip_pool {
start_address = "10.10.0.152"
end_address = "10.10.0.254"
}
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) A unique name for the network
* `edge_gateway` - (Required) The name of the edge gateway
* `netmask` - (Optional) The netmask for the new network. Defaults to `255.255.255.0`
* `gateway` (Required) The gateway for this network
* `dns1` - (Optional) First DNS server to use. Defaults to `8.8.8.8`
* `dns2` - (Optional) Second DNS server to use. Defaults to `8.8.4.4`
* `dns_suffix` - (Optional) A FQDN for the virtual machines on this network
* `dhcp_pool` - (Optional) A range of IPs to issue to virtual machines that don't
have a static IP; see [IP Pools](#ip-pools) below for details.
* `static_ip_pool` - (Optional) A range of IPs permitted to be used as static IPs for
virtual machines; see [IP Pools](#ip-pools) below for details.
<a id="ip-pools"></a>
## IP Pools
Network interfaces support the following attributes:
* `start_address` - (Required) The first address in the IP Range
* `end_address` - (Required) The final address in the IP Range

View File

@ -0,0 +1,30 @@
---
layout: "vcd"
page_title: "vCloudDirector: vcd_snat"
sidebar_current: "docs-vcd-resource-snat"
description: |-
Provides a vCloud Director SNAT resource. This can be used to create, modify, and delete source NATs to allow vApps to send external traffic.
---
# vcd\_snat
Provides a vCloud Director SNAT resource. This can be used to create, modify,
and delete source NATs to allow vApps to send external traffic.
## Example Usage
```
resource "vcd_snat" "outbound" {
edge_gateway = "Edge Gateway Name"
external_ip = "78.101.10.20"
internal_ip = "10.10.0.0/24"
}
```
## Argument Reference
The following arguments are supported:
* `edge_gateway` - (Required) The name of the edge gateway on which to apply the SNAT
* `external_ip` - (Required) One of the external IPs available on your Edge Gateway
* `internal_ip` - (Required) The IP or IP Range of the VM(s) to map from

View File

@ -0,0 +1,59 @@
---
layout: "vcd"
page_title: "vCloudDirector: vcd_vapp"
sidebar_current: "docs-vcd-resource-vapp"
description: |-
Provides a vCloud Director vApp resource. This can be used to create, modify, and delete vApps.
---
# vcd\_vapp
Provides a vCloud Director vApp resource. This can be used to create,
modify, and delete vApps.
## Example Usage
```
resource "vcd_network" "net" {
...
}
resource "vcd_vapp" "web" {
name = "web"
catalog_name = "Boxes"
template_name = "lampstack-1.10.1-ubuntu-10.04"
memory = 2048
cpus = 1
network_name = "${vcd_network.net.name}"
network_href = "${vcd_network.net.href}"
ip = "10.10.104.160"
metadata {
role = "web"
env = "staging"
version = "v1"
}
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) A unique name for the vApp
* `catalog_name` - (Required) The catalog name in which to find the given vApp Template
* `template_name` - (Required) The name of the vApp Template to use
* `memory` - (Optional) The amount of RAM (in MB) to allocate to the vApp
* `cpus` - (Optional) The number of virtual CPUs to allocate to the vApp
* `initscript` (Optional) A script to be run only on initial boot
* `network_name` - (Required) Name of the network this vApp should join
* `network_href` - (Optional) The vCloud Director generated href of the network this vApp
should join. If empty it will use the network name and query vCloud Director to discover
this
* `ip` - (Optional) The IP to assign to this vApp. If given the address must be within the `static_ip_pool`
set for the network. If left blank, and the network has `dhcp_pool` set with at least one available IP then
this will be set with DHCP
* `metadata` - (Optional) Key value map of metadata to assign to this vApp
* `power_on` - (Optional) A boolean value stating if this vApp should be powered on. Default to `true`

View File

@ -197,6 +197,10 @@
<a href="/docs/providers/tls/index.html">TLS</a>
</li>
<li<%= sidebar_current("docs-providers-vcd") %>>
<a href="/docs/providers/vcd/index.html">VMware vCloud Director</a>
</li>
<li<%= sidebar_current("docs-providers-vsphere") %>>
<a href="/docs/providers/vsphere/index.html">VMware vSphere</a>
</li>

View File

@ -0,0 +1,38 @@
<% wrap_layout :inner do %>
<% content_for :sidebar do %>
<div class="docs-sidebar hidden-print affix-top" role="complementary">
<ul class="nav docs-sidenav">
<li<%= sidebar_current("docs-home") %>>
<a href="/docs/providers/index.html">&laquo; Documentation Home</a>
</li>
<li<%= sidebar_current("docs-vcd-index") %>>
<a href="/docs/providers/vcd/index.html">VMware vCloudDirector Provider</a>
</li>
<li<%= sidebar_current(/^docs-vcd-resource/) %>>
<a href="#">Resources</a>
<ul class="nav nav-visible">
<li<%= sidebar_current("docs-vcd-resource-dnat") %>>
<a href="/docs/providers/vcd/r/dnat.html">vcd_dnat</a>
</li>
<li<%= sidebar_current("docs-vcd-resource-firewall-rules") %>>
<a href="/docs/providers/vcd/r/firewall_rules.html">vcd_firewall_rules</a>
</li>
<li<%= sidebar_current("docs-vcd-resource-network") %>>
<a href="/docs/providers/vcd/r/network.html">vcd_network</a>
</li>
<li<%= sidebar_current("docs-vcd-resource-snat") %>>
<a href="/docs/providers/vcd/r/snat.html">vcd_snat</a>
</li>
<li<%= sidebar_current("docs-vcd-resource-vapp") %>>
<a href="/docs/providers/vcd/r/vapp.html">vcd_vapp</a>
</li>
</ul>
</li>
</ul>
</div>
<% end %>
<%= yield %>
<% end %>