Merge branch 'master' into hmrc
This commit is contained in:
commit
cb90e6c037
|
@ -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,
|
||||
})
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
package vcd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/opencredo/vmware-govcd"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func resourceVcdDNAT() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourceVcdDNATCreate,
|
||||
Update: resourceVcdDNATUpdate,
|
||||
Delete: resourceVcdDNATDelete,
|
||||
Read: resourceVcdDNATRead,
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"edge_gateway": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"external_ip": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
|
||||
"port": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Required: true,
|
||||
},
|
||||
|
||||
"internal_ip": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceVcdDNATCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
vcd_client := meta.(*govcd.VCDClient)
|
||||
// Multiple VCD components need to run operations on the Edge Gateway, as
|
||||
// the edge gatway will throw back an error if it is already performing an
|
||||
// operation we must wait until we can aquire a lock on the client
|
||||
vcd_client.Mutex.Lock()
|
||||
defer vcd_client.Mutex.Unlock()
|
||||
portString := getPortString(d.Get("port").(int))
|
||||
|
||||
edgeGateway, err := vcd_client.OrgVdc.FindEdgeGateway(d.Get("edge_gateway").(string))
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to find edge gateway: %#v", err)
|
||||
}
|
||||
|
||||
// Creating a loop to offer further protection from the edge gateway erroring
|
||||
// due to being busy eg another person is using another client so wouldn't be
|
||||
// constrained by out lock. If the edge gateway reurns with a busy error, wait
|
||||
// 3 seconds and then try again. Continue until a non-busy error or success
|
||||
|
||||
err = retryCall(4, func() error {
|
||||
task, err := edgeGateway.AddNATMapping("DNAT", d.Get("external_ip").(string),
|
||||
d.Get("internal_ip").(string),
|
||||
portString)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error setting DNAT rules: %#v", err)
|
||||
}
|
||||
|
||||
return task.WaitTaskCompletion()
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error completing tasks: %#v", err)
|
||||
}
|
||||
|
||||
d.SetId(d.Get("external_ip").(string) + "_" + portString)
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceVcdDNATUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
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()
|
||||
portString := getPortString(d.Get("port").(int))
|
||||
|
||||
edgeGateway, err := vcd_client.OrgVdc.FindEdgeGateway(d.Get("edge_gateway").(string))
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to find edge gateway: %#v", err)
|
||||
}
|
||||
err = retryCall(4, func() error {
|
||||
task, err := edgeGateway.RemoveNATMapping("DNAT", d.Get("external_ip").(string),
|
||||
d.Get("internal_ip").(string),
|
||||
portString)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error setting DNAT rules: %#v", err)
|
||||
}
|
||||
|
||||
return task.WaitTaskCompletion()
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error completing tasks: %#v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
`
|
|
@ -0,0 +1,241 @@
|
|||
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))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to find edge gateway: %s", err)
|
||||
}
|
||||
|
||||
err = retryCall(5, func() error {
|
||||
edgeGateway.Refresh()
|
||||
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)
|
||||
}
|
||||
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 {
|
||||
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())
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
`
|
|
@ -0,0 +1,261 @@
|
|||
package vcd
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"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 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 = retryCall(4, func() error {
|
||||
return vcd_client.OrgVdc.CreateOrgVDCNetwork(newnetwork)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error: %#v", err)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if dhcp, ok := d.GetOk("dhcp_pool"); ok {
|
||||
err = retryCall(4, 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 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 = retryCall(4, 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())
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
`
|
|
@ -0,0 +1,126 @@
|
|||
package vcd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/opencredo/vmware-govcd"
|
||||
)
|
||||
|
||||
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()
|
||||
|
||||
// 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 := vcd_client.OrgVdc.FindEdgeGateway(d.Get("edge_gateway").(string))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to find edge gateway: %#v", err)
|
||||
}
|
||||
|
||||
err = retryCall(4, func() error {
|
||||
task, err := edgeGateway.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 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()
|
||||
|
||||
edgeGateway, err := vcd_client.OrgVdc.FindEdgeGateway(d.Get("edge_gateway").(string))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to find edge gateway: %#v", err)
|
||||
}
|
||||
|
||||
err = retryCall(4, func() error {
|
||||
task, err := edgeGateway.RemoveNATMapping("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
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
`
|
|
@ -0,0 +1,392 @@
|
|||
package vcd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/opencredo/vmware-govcd"
|
||||
types "github.com/opencredo/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 {
|
||||
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 = retryCall(4, func() error {
|
||||
e := vcd_client.OrgVdc.InstantiateVAppTemplate(createvapp)
|
||||
|
||||
if e != nil {
|
||||
return fmt.Errorf("Error: %#v", e)
|
||||
}
|
||||
|
||||
e = vcd_client.OrgVdc.Refresh()
|
||||
if e != nil {
|
||||
return fmt.Errorf("Error: %#v", e)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 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
|
||||
// }
|
||||
|
||||
vapp, err := vcd_client.OrgVdc.FindVAppByName(d.Get("name").(string))
|
||||
|
||||
err = retryCall(4, 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
|
||||
}
|
||||
|
||||
err = retryCall(4, 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)
|
||||
}
|
||||
|
||||
err = retryCall(4, 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(4, 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)
|
||||
}
|
||||
|
||||
err = retryCall(4, func() error {
|
||||
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)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error adding metadata: %#v", err)
|
||||
}
|
||||
|
||||
if initscript, ok := d.GetOk("initscript"); ok {
|
||||
err = retryCall(4, 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)
|
||||
}
|
||||
}
|
||||
|
||||
if d.Get("power_on").(bool) {
|
||||
err = retryCall(4, func() error {
|
||||
task, err := vapp.PowerOn()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error Powering Up: %#v", err)
|
||||
}
|
||||
return 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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
`
|
|
@ -0,0 +1,109 @@
|
|||
package vcd
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
types "github.com/opencredo/vmware-govcd/types/v56"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func retryCall(min int, f resource.RetryFunc) error {
|
||||
return resource.Retry(time.Duration(min)*time.Minute, f)
|
||||
}
|
|
@ -24,6 +24,7 @@ body.layout-packet,
|
|||
body.layout-rundeck,
|
||||
body.layout-template,
|
||||
body.layout-tls,
|
||||
body.layout-vcd,
|
||||
body.layout-vsphere,
|
||||
body.layout-docs,
|
||||
body.layout-downloads,
|
||||
|
|
|
@ -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.
|
|
@ -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
|
|
@ -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"
|
|
@ -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
|
|
@ -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
|
|
@ -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`
|
|
@ -193,9 +193,14 @@
|
|||
<a href="/docs/providers/tls/index.html">TLS</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-providers-vsphere") %>>
|
||||
<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>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
|
|
|
@ -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">« 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 %>
|
Loading…
Reference in New Issue