Added vCloud Director provider with tests and provider documentation

This commit is contained in:
Brett Mack 2015-10-26 14:45:48 +00:00
parent c817e7c765
commit 8780bd269a
24 changed files with 2421 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,32 @@
package vcd
import (
"fmt"
"net/url"
"github.com/opencredo/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
}

View File

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

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,171 @@
package vcd
import (
"fmt"
"github.com/hashicorp/terraform/helper/schema"
"github.com/opencredo/vmware-govcd"
"regexp"
"strings"
"time"
)
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()
var task govcd.Task
portString := getPortString(d.Get("port").(int))
// 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
for {
err := vcd_client.OrgVdc.Refresh()
if err != nil {
return fmt.Errorf("Error refreshing vdc: %#v", err)
}
edgeGateway, err := vcd_client.OrgVdc.FindEdgeGateway(d.Get("edge_gateway").(string))
if err != nil {
return fmt.Errorf("Unable to find edge gateway: %#v", err)
}
task, err = edgeGateway.AddNATMapping("DNAT", d.Get("external_ip").(string),
d.Get("internal_ip").(string),
portString)
if err != nil {
if v, _ := regexp.MatchString("is busy completing an operation.$", err.Error()); v {
time.Sleep(3 * time.Second)
continue
} else {
return fmt.Errorf("Error setting DNAT rules: %#v", err)
}
}
break
}
err := 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
}
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(), "_")
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()
var task govcd.Task
portString := getPortString(d.Get("port").(int))
// 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
for {
err := vcd_client.OrgVdc.Refresh()
if err != nil {
return fmt.Errorf("Error refreshing vdc: %#v", err)
}
edgeGateway, err := vcd_client.OrgVdc.FindEdgeGateway(d.Get("edge_gateway").(string))
if err != nil {
return fmt.Errorf("Unable to find edge gateway: %#v", err)
}
task, err = edgeGateway.RemoveNATMapping("DNAT", d.Get("external_ip").(string),
d.Get("internal_ip").(string),
portString)
if err != nil {
if v, _ := regexp.MatchString("is busy completing an operation.$", err.Error()); v {
time.Sleep(3 * time.Second)
continue
} else {
return fmt.Errorf("Error setting DNAT rules: %#v", err)
}
}
break
}
err := 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/opencredo/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().(*govcd.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().(*govcd.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,236 @@
package vcd
import (
"bytes"
"fmt"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/schema"
"github.com/opencredo/vmware-govcd"
types "github.com/opencredo/vmware-govcd/types/v56"
"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.TypeSet,
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,
},
},
},
Set: resourceVcdNetworkFirewallRuleHash,
},
},
}
}
func resourceVcdFirewallRulesCreate(d *schema.ResourceData, meta interface{}) error {
vcd_client := meta.(*govcd.VCDClient)
vcd_client.Mutex.Lock()
defer vcd_client.Mutex.Unlock()
edgeGateway, err := vcd_client.OrgVdc.FindEdgeGateway(d.Get("edge_gateway").(string))
firewallRules, _ := expandFirewallRules(d.Get("rule").(*schema.Set).List(), edgeGateway.EdgeGateway)
task, err := edgeGateway.CreateFirewallRules(d.Get("default_action").(string), firewallRules)
if err != nil {
return fmt.Errorf("Error setting firewall rules: %#v", err)
}
err = 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 {
vcd_client := meta.(*govcd.VCDClient)
vcd_client.Mutex.Lock()
defer vcd_client.Mutex.Unlock()
edgeGateway, err := vcd_client.OrgVdc.FindEdgeGateway(d.Get("edge_gateway").(string))
firewallRules := deleteFirewallRules(d.Get("rule").(*schema.Set).List(), 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 {
vcd_client := meta.(*govcd.VCDClient)
edgeGateway, err := vcd_client.OrgVdc.FindEdgeGateway(d.Get("edge_gateway").(string))
if err != nil {
return fmt.Errorf("Error finding edge gateway: %#v", err)
}
firewallRules := *edgeGateway.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.FirewallService
d.Set("rule", resourceVcdFirewallRulesGather(firewallRules.FirewallRule, d.Get("rule").(*schema.Set).List()))
d.Set("default_action", firewallRules.DefaultAction)
return nil
}
func deleteFirewallRules(configured []interface{}, gateway *types.EdgeGateway) []*types.FirewallRule {
firewallRules := gateway.Configuration.EdgeGatewayServiceConfiguration.FirewallService.FirewallRule
fwrules := make([]*types.FirewallRule, 0, len(firewallRules)-len(configured))
for _, f := range firewallRules {
keep := true
for _, r := range configured {
data := r.(map[string]interface{})
if data["id"].(string) != f.ID {
continue
}
keep = false
}
if keep {
fwrules = append(fwrules, f)
}
}
return fwrules
}
func resourceVcdFirewallRulesGather(rules []*types.FirewallRule, configured []interface{}) []map[string]interface{} {
fwrules := make([]map[string]interface{}, 0, len(configured))
for i := len(configured) - 1; i >= 0; i-- {
data := configured[i].(map[string]interface{})
rule, err := matchFirewallRule(data, rules)
if err != nil {
continue
}
fwrules = append(fwrules, rule)
}
return fwrules
}
func matchFirewallRule(data map[string]interface{}, rules []*types.FirewallRule) (map[string]interface{}, error) {
rule := make(map[string]interface{})
for _, m := range rules {
if data["id"].(string) == "" {
if data["description"].(string) == m.Description &&
data["policy"].(string) == m.Policy &&
data["protocol"].(string) == getProtocol(*m.Protocols) &&
data["destination_port"].(string) == getPortString(m.Port) &&
strings.ToLower(data["destination_ip"].(string)) == strings.ToLower(m.DestinationIP) &&
data["source_port"].(string) == getPortString(m.SourcePort) &&
strings.ToLower(data["source_ip"].(string)) == strings.ToLower(m.SourceIP) {
rule["id"] = m.ID
rule["description"] = m.Description
rule["policy"] = m.Policy
rule["protocol"] = getProtocol(*m.Protocols)
rule["destination_port"] = getPortString(m.Port)
rule["destination_ip"] = strings.ToLower(m.DestinationIP)
rule["source_port"] = getPortString(m.SourcePort)
rule["source_ip"] = strings.ToLower(m.SourceIP)
return rule, nil
}
} else {
if data["id"].(string) == m.ID {
rule["id"] = m.ID
rule["description"] = m.Description
rule["policy"] = m.Policy
rule["protocol"] = getProtocol(*m.Protocols)
rule["destination_port"] = getPortString(m.Port)
rule["destination_ip"] = strings.ToLower(m.DestinationIP)
rule["source_port"] = getPortString(m.SourcePort)
rule["source_ip"] = strings.ToLower(m.SourceIP)
return rule, nil
}
}
}
return rule, fmt.Errorf("Unable to find rule")
}
func resourceVcdNetworkFirewallRuleHash(v interface{}) int {
var buf bytes.Buffer
m := v.(map[string]interface{})
buf.WriteString(fmt.Sprintf("%s-",
strings.ToLower(m["description"].(string))))
buf.WriteString(fmt.Sprintf("%s-",
strings.ToLower(m["policy"].(string))))
buf.WriteString(fmt.Sprintf("%s-",
strings.ToLower(m["protocol"].(string))))
buf.WriteString(fmt.Sprintf("%s-",
strings.ToLower(m["destination_port"].(string))))
buf.WriteString(fmt.Sprintf("%s-",
strings.ToLower(m["destination_ip"].(string))))
buf.WriteString(fmt.Sprintf("%s-",
strings.ToLower(m["source_port"].(string))))
buf.WriteString(fmt.Sprintf("%s-",
strings.ToLower(m["source_ip"].(string))))
return hashcode.String(buf.String())
}

View File

@ -0,0 +1,105 @@
package vcd
import (
"fmt"
"testing"
//"regexp"
"log"
"os"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/opencredo/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().(*govcd.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"),
}
conn, _ := config.Client()
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,263 @@
package vcd
import (
"log"
"bytes"
"fmt"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"github.com/opencredo/vmware-govcd"
types "github.com/opencredo/vmware-govcd/types/v56"
"strings"
"time"
)
func resourceVcdNetwork() *schema.Resource {
return &schema.Resource{
Create: resourceVcdNetworkCreate,
Update: resourceVcdNetworkUpdate,
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,
Default: "natRouted",
},
"edge_gateway": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"netmask": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "255.255.255.0",
},
"gateway": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"dns1": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "8.8.8.8",
},
"dns2": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "8.8.4.4",
},
"dns_suffix": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"href": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"dhcp_pool": &schema.Schema{
Type: schema.TypeSet,
Optional: 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,
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 {
vcd_client := meta.(*govcd.VCDClient)
log.Printf("[TRACE] CLIENT: %#v", vcd_client)
vcd_client.Mutex.Lock()
defer vcd_client.Mutex.Unlock()
edgeGateway, err := vcd_client.OrgVdc.FindEdgeGateway(d.Get("edge_gateway").(string))
ipRanges, err := expandIpRange(d.Get("static_ip_pool").(*schema.Set).List())
if err != nil {
fmt.Printf("error: %v\n", err)
}
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 = vcd_client.OrgVdc.CreateOrgVDCNetwork(newnetwork)
if err != nil {
return fmt.Errorf("Error: %#v", err)
}
if dhcp, ok := d.GetOk("dhcp_pool"); ok {
err := vcd_client.OrgVdc.Refresh()
if err != nil {
return fmt.Errorf("Error refreshing vdc: %#v", err)
}
network, err := vcd_client.OrgVdc.FindVDCNetwork(d.Get("name").(string))
if err != nil {
return fmt.Errorf("Error finding network: %#v", err)
}
task, err := edgeGateway.AddDhcpPool(network.OrgVDCNetwork, dhcp.(*schema.Set).List())
if err != nil {
return fmt.Errorf("Error adding DHCP pool: %#v", err)
}
err = task.WaitTaskCompletion()
if err != nil {
return fmt.Errorf("Error completing tasks: %#v", err)
}
}
d.SetId(d.Get("name").(string))
return resourceVcdNetworkRead(d, meta)
}
func resourceVcdNetworkUpdate(d *schema.ResourceData, meta interface{}) error {
vcd_client := meta.(*govcd.VCDClient)
log.Printf("[DEBUG] VCD Client configuration: %#v", vcd_client)
return nil
}
func resourceVcdNetworkRead(d *schema.ResourceData, meta interface{}) error {
vcd_client := meta.(*govcd.VCDClient)
log.Printf("[DEBUG] VCD Client configuration: %#v", vcd_client)
log.Printf("[DEBUG] VCD Client configuration: %#v", vcd_client.OrgVdc)
err := vcd_client.OrgVdc.Refresh()
if err != nil {
return fmt.Errorf("Error refreshing vdc: %#v", err)
}
network, err := vcd_client.OrgVdc.FindVDCNetwork(d.Id())
if err != nil {
return fmt.Errorf("Error finding network: %#v", err)
}
d.Set("name", network.OrgVDCNetwork.Name)
d.Set("href", network.OrgVDCNetwork.HREF)
d.Set("fence_mode", network.OrgVDCNetwork.Configuration.FenceMode)
d.Set("gateway", network.OrgVDCNetwork.Configuration.IPScopes.IPScope.Gateway)
d.Set("netmask", network.OrgVDCNetwork.Configuration.IPScopes.IPScope.Netmask)
d.Set("dns1", network.OrgVDCNetwork.Configuration.IPScopes.IPScope.DNS1)
d.Set("dns2", network.OrgVDCNetwork.Configuration.IPScopes.IPScope.DNS2)
return nil
}
func resourceVcdNetworkDelete(d *schema.ResourceData, meta interface{}) error {
vcd_client := meta.(*govcd.VCDClient)
vcd_client.Mutex.Lock()
defer vcd_client.Mutex.Unlock()
err := vcd_client.OrgVdc.Refresh()
if err != nil {
return fmt.Errorf("Error refreshing vdc: %#v", err)
}
network, err := vcd_client.OrgVdc.FindVDCNetwork(d.Id())
if err != nil {
return fmt.Errorf("Error finding network: %#v", err)
}
err = resource.Retry(3*time.Minute, func() error {
task, err := network.Delete()
if err != nil {
return fmt.Errorf("Error Deleting Network: %#v", err)
}
err = task.WaitTaskCompletion()
if err != nil {
return fmt.Errorf("Error completing tasks: %#v", err)
}
return nil
})
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/opencredo/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().(*govcd.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().(*govcd.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,161 @@
package vcd
import (
"fmt"
"github.com/hashicorp/terraform/helper/schema"
"github.com/opencredo/vmware-govcd"
"regexp"
"time"
)
func resourceVcdSNAT() *schema.Resource {
return &schema.Resource{
Create: resourceVcdSNATCreate,
Update: resourceVcdSNATUpdate,
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,
},
"internal_ip": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
},
}
}
func resourceVcdSNATCreate(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()
var task govcd.Task
// 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
for {
err := vcd_client.OrgVdc.Refresh()
if err != nil {
return fmt.Errorf("Error refreshing vdc: %#v", err)
}
edgeGateway, err := vcd_client.OrgVdc.FindEdgeGateway(d.Get("edge_gateway").(string))
if err != nil {
return fmt.Errorf("Unable to find edge gateway: %#v", err)
}
task, err = edgeGateway.AddNATMapping("SNAT", d.Get("internal_ip").(string),
d.Get("external_ip").(string),
"any")
if err != nil {
if v, _ := regexp.MatchString("is busy completing an operation.$", err.Error()); v {
time.Sleep(3 * time.Second)
continue
} else {
return fmt.Errorf("Error setting SNAT rules: %#v", err)
}
}
break
}
err := task.WaitTaskCompletion()
if err != nil {
return fmt.Errorf("Error completing tasks: %#v", err)
}
d.SetId(d.Get("internal_ip").(string))
return nil
}
func resourceVcdSNATUpdate(d *schema.ResourceData, meta interface{}) error {
return nil
}
func resourceVcdSNATRead(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)
}
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 {
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()
var task govcd.Task
// 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
for {
err := vcd_client.OrgVdc.Refresh()
if err != nil {
return fmt.Errorf("Error refreshing vdc: %#v", err)
}
edgeGateway, err := vcd_client.OrgVdc.FindEdgeGateway(d.Get("edge_gateway").(string))
if err != nil {
return fmt.Errorf("Unable to find edge gateway: %#v", err)
}
task, err = edgeGateway.RemoveNATMapping("SNAT", d.Get("internal_ip").(string),
d.Get("external_ip").(string),
"")
if err != nil {
if v, _ := regexp.MatchString("is busy completing an operation.$", err.Error()); v {
time.Sleep(3 * time.Second)
continue
} else {
return fmt.Errorf("Error setting SNAT rules: %#v", err)
}
}
break
}
err := task.WaitTaskCompletion()
if err != nil {
return fmt.Errorf("Error completing tasks: %#v", 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/opencredo/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().(*govcd.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().(*govcd.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,355 @@
package vcd
import (
"fmt"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"github.com/opencredo/vmware-govcd"
types "github.com/opencredo/vmware-govcd/types/v56"
"log"
"time"
)
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 {
vcd_client := meta.(*govcd.VCDClient)
catalog, err := vcd_client.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 := vcd_client.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(&vcd_client.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 = resource.Retry(4*time.Minute, func() error {
err = vcd_client.OrgVdc.InstantiateVAppTemplate(createvapp)
if err != nil {
return fmt.Errorf("Error: %#v", err)
}
return nil
})
if err != nil {
return err
}
err = vcd_client.OrgVdc.Refresh()
if err != nil {
return fmt.Errorf("Error: %#v", err)
}
vapp, err := vcd_client.OrgVdc.FindVAppByName(d.Get("name").(string))
task, err := vapp.ChangeMemorySize(d.Get("memory").(int))
err = task.WaitTaskCompletion()
if err != nil {
return fmt.Errorf("Error changing memory size: %#v", err)
}
task, err = vapp.ChangeCPUcount(d.Get("cpus").(int))
err = task.WaitTaskCompletion()
if err != nil {
return fmt.Errorf("Error changing cpu count: %#v", err)
}
task, err = vapp.ChangeVMName(d.Get("name").(string))
if err != nil {
return fmt.Errorf("Error with vm name change: %#v", err)
}
err = task.WaitTaskCompletion()
if err != nil {
return fmt.Errorf("Error changing vmname: %#v", err)
}
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)
}
err = task.WaitTaskCompletion()
if err != nil {
return fmt.Errorf("Error changing network: %#v", err)
}
metadata := d.Get("metadata").(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 initscript, ok := d.GetOk("initscript"); ok {
task, err = vapp.RunCustomizationScript(d.Get("name").(string), initscript.(string))
if err != nil {
return fmt.Errorf("Error with setting init script: %#v", err)
}
err = task.WaitTaskCompletion()
if err != nil {
return fmt.Errorf("Error completing tasks: %#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)
}
}
d.SetId(d.Get("name").(string))
return resourceVcdVAppRead(d, meta)
//return nil
}
func resourceVcdVAppUpdate(d *schema.ResourceData, meta interface{}) error {
vcd_client := meta.(*govcd.VCDClient)
vapp, err := vcd_client.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") {
task, err := vapp.ChangeMemorySize(d.Get("memory").(int))
err = task.WaitTaskCompletion()
if err != nil {
return fmt.Errorf("Error changing memory size: %#v", err)
}
}
if d.HasChange("cpus") {
task, err := vapp.ChangeCPUcount(d.Get("cpus").(int))
err = task.WaitTaskCompletion()
if err != nil {
return fmt.Errorf("Error changing cpu count: %#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 {
vcd_client := meta.(*govcd.VCDClient)
err := vcd_client.OrgVdc.Refresh()
if err != nil {
return fmt.Errorf("Error refreshing vdc: %#v", err)
}
vapp, err := vcd_client.OrgVdc.FindVAppByName(d.Id())
if err != nil {
return fmt.Errorf("Error finding vapp: %#v", err)
}
d.Set("ip", vapp.VApp.Children.VM[0].NetworkConnectionSection.NetworkConnection.IPAddress)
return nil
}
func resourceVcdVAppDelete(d *schema.ResourceData, meta interface{}) error {
vcd_client := meta.(*govcd.VCDClient)
vapp, err := vcd_client.OrgVdc.FindVAppByName(d.Id())
if err != nil {
return fmt.Errorf("error finding vdc: %s", err)
}
task, err := vapp.Undeploy()
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)
}
task, err = vapp.Delete()
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)
}
return nil
}

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/opencredo/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: testAccCheckVcdVApp_powerOff,
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().(*govcd.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().(*govcd.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,103 @@
package vcd
import (
types "github.com/opencredo/vmware-govcd/types/v56"
"strconv"
)
func expandIpRange(configured []interface{}) (types.IPRanges, error) {
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, nil
}
func expandFirewallRules(configured []interface{}, gateway *types.EdgeGateway) ([]*types.FirewallRule, error) {
//firewallRules := make([]*types.FirewallRule, 0, len(configured))
firewallRules := gateway.Configuration.EdgeGatewayServiceConfiguration.FirewallService.FirewallRule
for i := len(configured) - 1; i >= 0; i-- {
data := configured[i].(map[string]interface{})
var protocol *types.FirewallRuleProtocols
switch data["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: data["description"].(string),
Policy: data["policy"].(string),
Protocols: protocol,
Port: getNumericPort(data["destination_port"]),
DestinationPortRange: data["destination_port"].(string),
DestinationIP: data["destination_ip"].(string),
SourcePort: getNumericPort(data["source_port"]),
SourcePortRange: data["source_port"].(string),
SourceIP: data["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
}

View File

@ -23,6 +23,7 @@ body.layout-openstack,
body.layout-packet,
body.layout-rundeck,
body.layout-template,
body.layout-vcd,
body.layout-vsphere,
body.layout-docs,
body.layout-downloads,

View File

@ -0,0 +1,54 @@
---
layout: "vcd"
page_title: "Provider: vCloudDirector"
sidebar_current: "docs-vcd-index"
description: |-
The vCloud Director provider is used to interact with the resources supported by vCloud
Director. The provider needs to be configured with the proper credentials before it can be used.
---
# vCloud Director Provider
The vCloud Director provider is used to interact with the resources supported by 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 vCloud Director Provider currently represents _initial support_ and
therefore may undergo significant changes as the community improves it.
## Example Usage
```
# Configure the 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}"
}
# Create a new network
resource "vcd_network" "net" {
...
}
```
## Argument Reference
The following arguments are used to configure the 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.

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 external IPs 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,63 @@
---
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 = "allow-web"
policy = "allow"
protocol = "tcp"
destination_port = "80"
destination_ip = "10.10.0.5"
source_port = "any"
source_ip = "any"
}
rule {
description = "allow-outbound"
policy = "allow"
protocol = "any"
destination_port = "any"
destination_ip = "any"
source_port = "any"
source_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 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

@ -189,6 +189,10 @@
<a href="/docs/providers/template/index.html">Template</a>
</li>
<li<%= sidebar_current("docs-providers-vcd") %>>
<a href="/docs/providers/vcd/index.html">vCloud Director</a>
</li>
<li<%= sidebar_current("docs-providers-vsphere") %>>
<a href="/docs/providers/vsphere/index.html">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">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 %>