From 8780bd269ae1678319be0129af6e79c4cf407e72 Mon Sep 17 00:00:00 2001 From: Brett Mack Date: Mon, 26 Oct 2015 14:45:48 +0000 Subject: [PATCH] Added vCloud Director provider with tests and provider documentation --- builtin/bins/provider-vcd/main.go | 12 + builtin/providers/vcd/config.go | 32 ++ builtin/providers/vcd/provider.go | 69 ++++ builtin/providers/vcd/provider_test.go | 50 +++ builtin/providers/vcd/resource_vcd_dnat.go | 171 +++++++++ .../providers/vcd/resource_vcd_dnat_test.go | 120 ++++++ .../vcd/resource_vcd_firewall_rules.go | 236 ++++++++++++ .../vcd/resource_vcd_firewall_rules_test.go | 105 ++++++ builtin/providers/vcd/resource_vcd_network.go | 263 +++++++++++++ .../vcd/resource_vcd_network_test.go | 107 ++++++ builtin/providers/vcd/resource_vcd_snat.go | 161 ++++++++ .../providers/vcd/resource_vcd_snat_test.go | 119 ++++++ builtin/providers/vcd/resource_vcd_vapp.go | 355 ++++++++++++++++++ .../providers/vcd/resource_vcd_vapp_test.go | 180 +++++++++ builtin/providers/vcd/structure.go | 103 +++++ website/source/assets/stylesheets/_docs.scss | 1 + .../docs/providers/vcd/index.html.markdown | 54 +++ .../docs/providers/vcd/r/dnat.html.markdown | 32 ++ .../vcd/r/firewall_rules.html.markdown | 63 ++++ .../providers/vcd/r/network.html.markdown | 57 +++ .../docs/providers/vcd/r/snat.html.markdown | 30 ++ .../docs/providers/vcd/r/vapp.html.markdown | 59 +++ website/source/layouts/docs.erb | 4 + website/source/layouts/vcd.erb | 38 ++ 24 files changed, 2421 insertions(+) create mode 100644 builtin/bins/provider-vcd/main.go create mode 100644 builtin/providers/vcd/config.go create mode 100644 builtin/providers/vcd/provider.go create mode 100644 builtin/providers/vcd/provider_test.go create mode 100644 builtin/providers/vcd/resource_vcd_dnat.go create mode 100644 builtin/providers/vcd/resource_vcd_dnat_test.go create mode 100644 builtin/providers/vcd/resource_vcd_firewall_rules.go create mode 100644 builtin/providers/vcd/resource_vcd_firewall_rules_test.go create mode 100644 builtin/providers/vcd/resource_vcd_network.go create mode 100644 builtin/providers/vcd/resource_vcd_network_test.go create mode 100644 builtin/providers/vcd/resource_vcd_snat.go create mode 100644 builtin/providers/vcd/resource_vcd_snat_test.go create mode 100644 builtin/providers/vcd/resource_vcd_vapp.go create mode 100644 builtin/providers/vcd/resource_vcd_vapp_test.go create mode 100644 builtin/providers/vcd/structure.go create mode 100644 website/source/docs/providers/vcd/index.html.markdown create mode 100644 website/source/docs/providers/vcd/r/dnat.html.markdown create mode 100644 website/source/docs/providers/vcd/r/firewall_rules.html.markdown create mode 100644 website/source/docs/providers/vcd/r/network.html.markdown create mode 100644 website/source/docs/providers/vcd/r/snat.html.markdown create mode 100644 website/source/docs/providers/vcd/r/vapp.html.markdown create mode 100644 website/source/layouts/vcd.erb diff --git a/builtin/bins/provider-vcd/main.go b/builtin/bins/provider-vcd/main.go new file mode 100644 index 000000000..7e040dd43 --- /dev/null +++ b/builtin/bins/provider-vcd/main.go @@ -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, + }) +} diff --git a/builtin/providers/vcd/config.go b/builtin/providers/vcd/config.go new file mode 100644 index 000000000..0768bbc3d --- /dev/null +++ b/builtin/providers/vcd/config.go @@ -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 +} diff --git a/builtin/providers/vcd/provider.go b/builtin/providers/vcd/provider.go new file mode 100644 index 000000000..c9849be35 --- /dev/null +++ b/builtin/providers/vcd/provider.go @@ -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() +} diff --git a/builtin/providers/vcd/provider_test.go b/builtin/providers/vcd/provider_test.go new file mode 100644 index 000000000..48ee20721 --- /dev/null +++ b/builtin/providers/vcd/provider_test.go @@ -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") + } +} diff --git a/builtin/providers/vcd/resource_vcd_dnat.go b/builtin/providers/vcd/resource_vcd_dnat.go new file mode 100644 index 000000000..dd1c67e33 --- /dev/null +++ b/builtin/providers/vcd/resource_vcd_dnat.go @@ -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 +} diff --git a/builtin/providers/vcd/resource_vcd_dnat_test.go b/builtin/providers/vcd/resource_vcd_dnat_test.go new file mode 100644 index 000000000..ba4bfce13 --- /dev/null +++ b/builtin/providers/vcd/resource_vcd_dnat_test.go @@ -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" +} +` diff --git a/builtin/providers/vcd/resource_vcd_firewall_rules.go b/builtin/providers/vcd/resource_vcd_firewall_rules.go new file mode 100644 index 000000000..e025b143f --- /dev/null +++ b/builtin/providers/vcd/resource_vcd_firewall_rules.go @@ -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()) +} diff --git a/builtin/providers/vcd/resource_vcd_firewall_rules_test.go b/builtin/providers/vcd/resource_vcd_firewall_rules_test.go new file mode 100644 index 000000000..96e2c3e3d --- /dev/null +++ b/builtin/providers/vcd/resource_vcd_firewall_rules_test.go @@ -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" + } +} +` diff --git a/builtin/providers/vcd/resource_vcd_network.go b/builtin/providers/vcd/resource_vcd_network.go new file mode 100644 index 000000000..3196b7306 --- /dev/null +++ b/builtin/providers/vcd/resource_vcd_network.go @@ -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()) +} diff --git a/builtin/providers/vcd/resource_vcd_network_test.go b/builtin/providers/vcd/resource_vcd_network_test.go new file mode 100644 index 000000000..6bfd840bb --- /dev/null +++ b/builtin/providers/vcd/resource_vcd_network_test.go @@ -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" + } +} +` diff --git a/builtin/providers/vcd/resource_vcd_snat.go b/builtin/providers/vcd/resource_vcd_snat.go new file mode 100644 index 000000000..b9627e03a --- /dev/null +++ b/builtin/providers/vcd/resource_vcd_snat.go @@ -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 +} diff --git a/builtin/providers/vcd/resource_vcd_snat_test.go b/builtin/providers/vcd/resource_vcd_snat_test.go new file mode 100644 index 000000000..bf3eced14 --- /dev/null +++ b/builtin/providers/vcd/resource_vcd_snat_test.go @@ -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" +} +` diff --git a/builtin/providers/vcd/resource_vcd_vapp.go b/builtin/providers/vcd/resource_vcd_vapp.go new file mode 100644 index 000000000..7e760ac1d --- /dev/null +++ b/builtin/providers/vcd/resource_vcd_vapp.go @@ -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 +} diff --git a/builtin/providers/vcd/resource_vcd_vapp_test.go b/builtin/providers/vcd/resource_vcd_vapp_test.go new file mode 100644 index 000000000..bb6e9874a --- /dev/null +++ b/builtin/providers/vcd/resource_vcd_vapp_test.go @@ -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 +} +` diff --git a/builtin/providers/vcd/structure.go b/builtin/providers/vcd/structure.go new file mode 100644 index 000000000..9cd5fb281 --- /dev/null +++ b/builtin/providers/vcd/structure.go @@ -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 +} diff --git a/website/source/assets/stylesheets/_docs.scss b/website/source/assets/stylesheets/_docs.scss index 0defd251a..ab9dae99c 100755 --- a/website/source/assets/stylesheets/_docs.scss +++ b/website/source/assets/stylesheets/_docs.scss @@ -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, diff --git a/website/source/docs/providers/vcd/index.html.markdown b/website/source/docs/providers/vcd/index.html.markdown new file mode 100644 index 000000000..45cb0df58 --- /dev/null +++ b/website/source/docs/providers/vcd/index.html.markdown @@ -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. diff --git a/website/source/docs/providers/vcd/r/dnat.html.markdown b/website/source/docs/providers/vcd/r/dnat.html.markdown new file mode 100644 index 000000000..dcaed4baa --- /dev/null +++ b/website/source/docs/providers/vcd/r/dnat.html.markdown @@ -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 diff --git a/website/source/docs/providers/vcd/r/firewall_rules.html.markdown b/website/source/docs/providers/vcd/r/firewall_rules.html.markdown new file mode 100644 index 000000000..e8fb4401d --- /dev/null +++ b/website/source/docs/providers/vcd/r/firewall_rules.html.markdown @@ -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. + + +## 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" diff --git a/website/source/docs/providers/vcd/r/network.html.markdown b/website/source/docs/providers/vcd/r/network.html.markdown new file mode 100644 index 000000000..eead8c58e --- /dev/null +++ b/website/source/docs/providers/vcd/r/network.html.markdown @@ -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. + + +## 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 diff --git a/website/source/docs/providers/vcd/r/snat.html.markdown b/website/source/docs/providers/vcd/r/snat.html.markdown new file mode 100644 index 000000000..dc8b567c7 --- /dev/null +++ b/website/source/docs/providers/vcd/r/snat.html.markdown @@ -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 diff --git a/website/source/docs/providers/vcd/r/vapp.html.markdown b/website/source/docs/providers/vcd/r/vapp.html.markdown new file mode 100644 index 000000000..0a2a2e234 --- /dev/null +++ b/website/source/docs/providers/vcd/r/vapp.html.markdown @@ -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` diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index 937c120de..0e3923b86 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -189,6 +189,10 @@ Template + > + vCloud Director + + > vSphere diff --git a/website/source/layouts/vcd.erb b/website/source/layouts/vcd.erb new file mode 100644 index 000000000..ebfd9b7d9 --- /dev/null +++ b/website/source/layouts/vcd.erb @@ -0,0 +1,38 @@ +<% wrap_layout :inner do %> + <% content_for :sidebar do %> + + <% end %> + + <%= yield %> +<% end %>