diff --git a/builtin/bins/provider-vcd/main.go b/builtin/bins/provider-vcd/main.go new file mode 100644 index 000000000000..7e040dd432c4 --- /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 000000000000..c6b5ba509ae6 --- /dev/null +++ b/builtin/providers/vcd/config.go @@ -0,0 +1,40 @@ +package vcd + +import ( + "fmt" + "net/url" + + "github.com/hmrc/vmware-govcd" +) + +type Config struct { + User string + Password string + Org string + Href string + VDC string + MaxRetryTimeout int +} + +type VCDClient struct { + *govcd.VCDClient + MaxRetryTimeout int +} + +func (c *Config) Client() (*VCDClient, error) { + u, err := url.ParseRequestURI(c.Href) + if err != nil { + return nil, fmt.Errorf("Something went wrong: %s", err) + } + + vcdclient := &VCDClient{ + govcd.NewVCDClient(*u), + c.MaxRetryTimeout} + org, vcd, err := vcdclient.Authenticate(c.User, c.Password, c.Org, c.VDC) + if err != nil { + return nil, fmt.Errorf("Something went wrong: %s", err) + } + vcdclient.Org = org + vcdclient.OrgVdc = vcd + return vcdclient, nil +} diff --git a/builtin/providers/vcd/provider.go b/builtin/providers/vcd/provider.go new file mode 100644 index 000000000000..aab15cedd392 --- /dev/null +++ b/builtin/providers/vcd/provider.go @@ -0,0 +1,78 @@ +package vcd + +import ( + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" +) + +// Provider returns a terraform.ResourceProvider. +func Provider() terraform.ResourceProvider { + return &schema.Provider{ + Schema: map[string]*schema.Schema{ + "user": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("VCD_USER", nil), + Description: "The user name for vcd API operations.", + }, + + "password": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("VCD_PASSWORD", nil), + Description: "The user password for vcd API operations.", + }, + + "org": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("VCD_ORG", nil), + Description: "The vcd org for API operations", + }, + + "url": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("VCD_URL", nil), + Description: "The vcd url for vcd API operations.", + }, + + "vdc": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("VCD_VDC", ""), + Description: "The name of the VDC to run operations on", + }, + + "maxRetryTimeout": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("VCD_MAX_RETRY_TIMEOUT", 60), + Description: "Max num seconds to wait for successful response when operating on resources within vCloud (defaults to 60)", + }, + }, + + ResourcesMap: map[string]*schema.Resource{ + "vcd_network": resourceVcdNetwork(), + "vcd_vapp": resourceVcdVApp(), + "vcd_firewall_rules": resourceVcdFirewallRules(), + "vcd_dnat": resourceVcdDNAT(), + "vcd_snat": resourceVcdSNAT(), + }, + + ConfigureFunc: providerConfigure, + } +} + +func providerConfigure(d *schema.ResourceData) (interface{}, error) { + config := Config{ + User: d.Get("user").(string), + Password: d.Get("password").(string), + Org: d.Get("org").(string), + Href: d.Get("url").(string), + VDC: d.Get("vdc").(string), + MaxRetryTimeout: d.Get("maxRetryTimeout").(int), + } + + return config.Client() +} diff --git a/builtin/providers/vcd/provider_test.go b/builtin/providers/vcd/provider_test.go new file mode 100644 index 000000000000..48ee20721911 --- /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 000000000000..5c2e8006c171 --- /dev/null +++ b/builtin/providers/vcd/resource_vcd_dnat.go @@ -0,0 +1,135 @@ +package vcd + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceVcdDNAT() *schema.Resource { + return &schema.Resource{ + Create: resourceVcdDNATCreate, + Delete: resourceVcdDNATDelete, + Read: resourceVcdDNATRead, + + Schema: map[string]*schema.Schema{ + "edge_gateway": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "external_ip": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "port": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + + "internal_ip": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceVcdDNATCreate(d *schema.ResourceData, meta interface{}) error { + vcdClient := meta.(*VCDClient) + // Multiple VCD components need to run operations on the Edge Gateway, as + // the edge gatway will throw back an error if it is already performing an + // operation we must wait until we can aquire a lock on the client + vcdClient.Mutex.Lock() + defer vcdClient.Mutex.Unlock() + portString := getPortString(d.Get("port").(int)) + + edgeGateway, err := vcdClient.OrgVdc.FindEdgeGateway(d.Get("edge_gateway").(string)) + + if err != nil { + return fmt.Errorf("Unable to find edge gateway: %#v", err) + } + + // Creating a loop to offer further protection from the edge gateway erroring + // due to being busy eg another person is using another client so wouldn't be + // constrained by out lock. If the edge gateway reurns with a busy error, wait + // 3 seconds and then try again. Continue until a non-busy error or success + + err = retryCall(vcdClient.MaxRetryTimeout, func() error { + task, err := edgeGateway.AddNATMapping("DNAT", d.Get("external_ip").(string), + d.Get("internal_ip").(string), + portString) + if err != nil { + return fmt.Errorf("Error setting DNAT rules: %#v", err) + } + + return task.WaitTaskCompletion() + }) + + if err != nil { + return fmt.Errorf("Error completing tasks: %#v", err) + } + + d.SetId(d.Get("external_ip").(string) + "_" + portString) + return nil +} + +func resourceVcdDNATRead(d *schema.ResourceData, meta interface{}) error { + vcdClient := meta.(*VCDClient) + e, err := vcdClient.OrgVdc.FindEdgeGateway(d.Get("edge_gateway").(string)) + + if err != nil { + return fmt.Errorf("Unable to find edge gateway: %#v", err) + } + + var found bool + + for _, r := range e.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService.NatRule { + if r.RuleType == "DNAT" && + r.GatewayNatRule.OriginalIP == d.Get("external_ip").(string) && + r.GatewayNatRule.OriginalPort == getPortString(d.Get("port").(int)) { + found = true + d.Set("internal_ip", r.GatewayNatRule.TranslatedIP) + } + } + + if !found { + d.SetId("") + } + + return nil +} + +func resourceVcdDNATDelete(d *schema.ResourceData, meta interface{}) error { + vcdClient := meta.(*VCDClient) + // Multiple VCD components need to run operations on the Edge Gateway, as + // the edge gatway will throw back an error if it is already performing an + // operation we must wait until we can aquire a lock on the client + vcdClient.Mutex.Lock() + defer vcdClient.Mutex.Unlock() + portString := getPortString(d.Get("port").(int)) + + edgeGateway, err := vcdClient.OrgVdc.FindEdgeGateway(d.Get("edge_gateway").(string)) + + if err != nil { + return fmt.Errorf("Unable to find edge gateway: %#v", err) + } + err = retryCall(vcdClient.MaxRetryTimeout, func() error { + task, err := edgeGateway.RemoveNATMapping("DNAT", d.Get("external_ip").(string), + d.Get("internal_ip").(string), + portString) + if err != nil { + return fmt.Errorf("Error setting DNAT rules: %#v", err) + } + + return task.WaitTaskCompletion() + }) + if err != nil { + return fmt.Errorf("Error completing tasks: %#v", err) + } + return nil +} 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 000000000000..759d9d16b840 --- /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/hmrc/vmware-govcd" +) + +func TestAccVcdDNAT_Basic(t *testing.T) { + if v := os.Getenv("VCD_EXTERNAL_IP"); v == "" { + t.Skip("Environment variable VCD_EXTERNAL_IP must be set to run DNAT tests") + return + } + + var e govcd.EdgeGateway + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVcdDNATDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: fmt.Sprintf(testAccCheckVcdDnat_basic, os.Getenv("VCD_EDGE_GATWEWAY"), os.Getenv("VCD_EXTERNAL_IP")), + Check: resource.ComposeTestCheckFunc( + testAccCheckVcdDNATExists("vcd_dnat.bar", &e), + resource.TestCheckResourceAttr( + "vcd_dnat.bar", "external_ip", os.Getenv("VCD_EXTERNAL_IP")), + resource.TestCheckResourceAttr( + "vcd_dnat.bar", "port", "77"), + resource.TestCheckResourceAttr( + "vcd_dnat.bar", "internal_ip", "10.10.102.60"), + ), + }, + }, + }) +} + +func testAccCheckVcdDNATExists(n string, gateway *govcd.EdgeGateway) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No DNAT ID is set") + } + + conn := testAccProvider.Meta().(*VCDClient) + + gatewayName := rs.Primary.Attributes["edge_gateway"] + edgeGateway, err := conn.OrgVdc.FindEdgeGateway(gatewayName) + + if err != nil { + return fmt.Errorf("Could not find edge gateway") + } + + var found bool + for _, v := range edgeGateway.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService.NatRule { + if v.RuleType == "DNAT" && + v.GatewayNatRule.OriginalIP == os.Getenv("VCD_EXTERNAL_IP") && + v.GatewayNatRule.OriginalPort == "77" && + v.GatewayNatRule.TranslatedIP == "10.10.102.60" { + found = true + } + } + if !found { + return fmt.Errorf("DNAT rule was not found") + } + + *gateway = edgeGateway + + return nil + } +} + +func testAccCheckVcdDNATDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*VCDClient) + for _, rs := range s.RootModule().Resources { + if rs.Type != "vcd_dnat" { + continue + } + + gatewayName := rs.Primary.Attributes["edge_gateway"] + edgeGateway, err := conn.OrgVdc.FindEdgeGateway(gatewayName) + + if err != nil { + return fmt.Errorf("Could not find edge gateway") + } + + var found bool + for _, v := range edgeGateway.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService.NatRule { + if v.RuleType == "DNAT" && + v.GatewayNatRule.OriginalIP == os.Getenv("VCD_EXTERNAL_IP") && + v.GatewayNatRule.OriginalPort == "77" && + v.GatewayNatRule.TranslatedIP == "10.10.102.60" { + found = true + } + } + + if found { + return fmt.Errorf("DNAT rule still exists.") + } + } + + return nil +} + +const testAccCheckVcdDnat_basic = ` +resource "vcd_dnat" "bar" { + edge_gateway = "%s" + external_ip = "%s" + port = 77 + internal_ip = "10.10.102.60" +} +` 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 000000000000..913bff8be077 --- /dev/null +++ b/builtin/providers/vcd/resource_vcd_firewall_rules.go @@ -0,0 +1,197 @@ +package vcd + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/schema" + types "github.com/hmrc/vmware-govcd/types/v56" + "log" + "strings" +) + +func resourceVcdFirewallRules() *schema.Resource { + return &schema.Resource{ + Create: resourceVcdFirewallRulesCreate, + Delete: resourceFirewallRulesDelete, + Read: resourceFirewallRulesRead, + + Schema: map[string]*schema.Schema{ + "edge_gateway": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "default_action": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "rule": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "description": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "policy": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "protocol": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "destination_port": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "destination_ip": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "source_port": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "source_ip": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + } +} + +func resourceVcdFirewallRulesCreate(d *schema.ResourceData, meta interface{}) error { + vcdClient := meta.(*VCDClient) + vcdClient.Mutex.Lock() + defer vcdClient.Mutex.Unlock() + + edgeGateway, err := vcdClient.OrgVdc.FindEdgeGateway(d.Get("edge_gateway").(string)) + if err != nil { + return fmt.Errorf("Unable to find edge gateway: %s", err) + } + + err = retryCall(vcdClient.MaxRetryTimeout, func() error { + edgeGateway.Refresh() + firewallRules, _ := expandFirewallRules(d, edgeGateway.EdgeGateway) + task, err := edgeGateway.CreateFirewallRules(d.Get("default_action").(string), firewallRules) + if err != nil { + log.Printf("[INFO] Error setting firewall rules: %s", err) + return fmt.Errorf("Error setting firewall rules: %#v", err) + } + + return task.WaitTaskCompletion() + }) + if err != nil { + return fmt.Errorf("Error completing tasks: %#v", err) + } + + d.SetId(d.Get("edge_gateway").(string)) + + return resourceFirewallRulesRead(d, meta) +} + +func resourceFirewallRulesDelete(d *schema.ResourceData, meta interface{}) error { + vcdClient := meta.(*VCDClient) + vcdClient.Mutex.Lock() + defer vcdClient.Mutex.Unlock() + + edgeGateway, err := vcdClient.OrgVdc.FindEdgeGateway(d.Get("edge_gateway").(string)) + + firewallRules := deleteFirewallRules(d, edgeGateway.EdgeGateway) + defaultAction := edgeGateway.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.FirewallService.DefaultAction + task, err := edgeGateway.CreateFirewallRules(defaultAction, firewallRules) + if err != nil { + return fmt.Errorf("Error deleting firewall rules: %#v", err) + } + + err = task.WaitTaskCompletion() + if err != nil { + return fmt.Errorf("Error completing tasks: %#v", err) + } + + return nil +} + +func resourceFirewallRulesRead(d *schema.ResourceData, meta interface{}) error { + vcdClient := meta.(*VCDClient) + + edgeGateway, err := vcdClient.OrgVdc.FindEdgeGateway(d.Get("edge_gateway").(string)) + if err != nil { + return fmt.Errorf("Error finding edge gateway: %#v", err) + } + ruleList := d.Get("rule").([]interface{}) + firewallRules := *edgeGateway.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.FirewallService + rulesCount := d.Get("rule.#").(int) + for i := 0; i < rulesCount; i++ { + prefix := fmt.Sprintf("rule.%d", i) + if d.Get(prefix+".id").(string) == "" { + log.Printf("[INFO] Rule %d has no id. Searching...", i) + ruleid, err := matchFirewallRule(d, prefix, firewallRules.FirewallRule) + if err == nil { + currentRule := ruleList[i].(map[string]interface{}) + currentRule["id"] = ruleid + ruleList[i] = currentRule + } + } + } + d.Set("rule", ruleList) + d.Set("default_action", firewallRules.DefaultAction) + + return nil +} + +func deleteFirewallRules(d *schema.ResourceData, gateway *types.EdgeGateway) []*types.FirewallRule { + firewallRules := gateway.Configuration.EdgeGatewayServiceConfiguration.FirewallService.FirewallRule + rulesCount := d.Get("rule.#").(int) + fwrules := make([]*types.FirewallRule, 0, len(firewallRules)-rulesCount) + + for _, f := range firewallRules { + keep := true + for i := 0; i < rulesCount; i++ { + if d.Get(fmt.Sprintf("rule.%d.id", i)).(string) != f.ID { + continue + } + keep = false + } + if keep { + fwrules = append(fwrules, f) + } + } + return fwrules +} + +func matchFirewallRule(d *schema.ResourceData, prefix string, rules []*types.FirewallRule) (string, error) { + + for _, m := range rules { + if d.Get(prefix+".description").(string) == m.Description && + d.Get(prefix+".policy").(string) == m.Policy && + strings.ToLower(d.Get(prefix+".protocol").(string)) == getProtocol(*m.Protocols) && + strings.ToLower(d.Get(prefix+".destination_port").(string)) == getPortString(m.Port) && + strings.ToLower(d.Get(prefix+".destination_ip").(string)) == strings.ToLower(m.DestinationIP) && + strings.ToLower(d.Get(prefix+".source_port").(string)) == getPortString(m.SourcePort) && + strings.ToLower(d.Get(prefix+".source_ip").(string)) == strings.ToLower(m.SourceIP) { + return m.ID, nil + } + } + return "", fmt.Errorf("Unable to find rule") +} 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 000000000000..2c1fa69e6b3a --- /dev/null +++ b/builtin/providers/vcd/resource_vcd_firewall_rules_test.go @@ -0,0 +1,108 @@ +package vcd + +import ( + "fmt" + "log" + "os" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/hmrc/vmware-govcd" +) + +func TestAccVcdFirewallRules_basic(t *testing.T) { + + var existingRules, fwRules govcd.EdgeGateway + newConfig := createFirewallRulesConfigs(&existingRules) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: newConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckVcdFirewallRulesExists("vcd_firewall_rules.bar", &fwRules), + testAccCheckVcdFirewallRulesAttributes(&fwRules, &existingRules), + ), + }, + }, + }) + +} + +func testAccCheckVcdFirewallRulesExists(n string, gateway *govcd.EdgeGateway) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Record ID is set") + } + + conn := testAccProvider.Meta().(*VCDClient) + + resp, err := conn.OrgVdc.FindEdgeGateway(rs.Primary.ID) + if err != nil { + return fmt.Errorf("Edge Gateway does not exist.") + } + + *gateway = resp + + return nil + } +} + +func testAccCheckVcdFirewallRulesAttributes(newRules, existingRules *govcd.EdgeGateway) resource.TestCheckFunc { + return func(s *terraform.State) error { + + if len(newRules.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.FirewallService.FirewallRule) != len(existingRules.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.FirewallService.FirewallRule)+1 { + return fmt.Errorf("New firewall rule not added: %d != %d", + len(newRules.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.FirewallService.FirewallRule), + len(existingRules.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.FirewallService.FirewallRule)+1) + } + + return nil + } +} + +func createFirewallRulesConfigs(existingRules *govcd.EdgeGateway) string { + config := Config{ + User: os.Getenv("VCD_USER"), + Password: os.Getenv("VCD_PASSWORD"), + Org: os.Getenv("VCD_ORG"), + Href: os.Getenv("VCD_URL"), + VDC: os.Getenv("VCD_VDC"), + MaxRetryTimeout: 240, + } + conn, err := config.Client() + if err != nil { + return fmt.Sprintf(testAccCheckVcdFirewallRules_add, "", "") + } + edgeGateway, _ := conn.OrgVdc.FindEdgeGateway(os.Getenv("VCD_EDGE_GATWEWAY")) + *existingRules = edgeGateway + log.Printf("[DEBUG] Edge gateway: %#v", edgeGateway) + firewallRules := *edgeGateway.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.FirewallService + return fmt.Sprintf(testAccCheckVcdFirewallRules_add, os.Getenv("VCD_EDGE_GATEWAY"), firewallRules.DefaultAction) +} + +const testAccCheckVcdFirewallRules_add = ` +resource "vcd_firewall_rules" "bar" { + edge_gateway = "%s" + default_action = "%s" + + rule { + description = "Test rule" + policy = "allow" + protocol = "any" + destination_port = "any" + destination_ip = "any" + source_port = "any" + source_ip = "any" + } +} +` diff --git a/builtin/providers/vcd/resource_vcd_network.go b/builtin/providers/vcd/resource_vcd_network.go new file mode 100644 index 000000000000..531afd878dc0 --- /dev/null +++ b/builtin/providers/vcd/resource_vcd_network.go @@ -0,0 +1,264 @@ +package vcd + +import ( + "log" + + "bytes" + "fmt" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" + types "github.com/hmrc/vmware-govcd/types/v56" + "strings" +) + +func resourceVcdNetwork() *schema.Resource { + return &schema.Resource{ + Create: resourceVcdNetworkCreate, + Read: resourceVcdNetworkRead, + Delete: resourceVcdNetworkDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "fence_mode": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "natRouted", + }, + + "edge_gateway": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "netmask": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "255.255.255.0", + }, + + "gateway": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "dns1": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "8.8.8.8", + }, + + "dns2": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "8.8.4.4", + }, + + "dns_suffix": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "href": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "dhcp_pool": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "start_address": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "end_address": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + }, + }, + Set: resourceVcdNetworkIPAddressHash, + }, + "static_ip_pool": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "start_address": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "end_address": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + }, + }, + Set: resourceVcdNetworkIPAddressHash, + }, + }, + } +} + +func resourceVcdNetworkCreate(d *schema.ResourceData, meta interface{}) error { + vcdClient := meta.(*VCDClient) + log.Printf("[TRACE] CLIENT: %#v", vcdClient) + vcdClient.Mutex.Lock() + defer vcdClient.Mutex.Unlock() + + edgeGateway, err := vcdClient.OrgVdc.FindEdgeGateway(d.Get("edge_gateway").(string)) + + ipRanges := expandIPRange(d.Get("static_ip_pool").(*schema.Set).List()) + + newnetwork := &types.OrgVDCNetwork{ + Xmlns: "http://www.vmware.com/vcloud/v1.5", + Name: d.Get("name").(string), + Configuration: &types.NetworkConfiguration{ + FenceMode: d.Get("fence_mode").(string), + IPScopes: &types.IPScopes{ + IPScope: types.IPScope{ + IsInherited: false, + Gateway: d.Get("gateway").(string), + Netmask: d.Get("netmask").(string), + DNS1: d.Get("dns1").(string), + DNS2: d.Get("dns2").(string), + DNSSuffix: d.Get("dns_suffix").(string), + IPRanges: &ipRanges, + }, + }, + BackwardCompatibilityMode: true, + }, + EdgeGateway: &types.Reference{ + HREF: edgeGateway.EdgeGateway.HREF, + }, + IsShared: false, + } + + log.Printf("[INFO] NETWORK: %#v", newnetwork) + + err = retryCall(vcdClient.MaxRetryTimeout, func() error { + return vcdClient.OrgVdc.CreateOrgVDCNetwork(newnetwork) + }) + if err != nil { + return fmt.Errorf("Error: %#v", err) + } + + err = vcdClient.OrgVdc.Refresh() + if err != nil { + return fmt.Errorf("Error refreshing vdc: %#v", err) + } + + network, err := vcdClient.OrgVdc.FindVDCNetwork(d.Get("name").(string)) + if err != nil { + return fmt.Errorf("Error finding network: %#v", err) + } + + if dhcp, ok := d.GetOk("dhcp_pool"); ok { + err = retryCall(vcdClient.MaxRetryTimeout, func() error { + task, err := edgeGateway.AddDhcpPool(network.OrgVDCNetwork, dhcp.(*schema.Set).List()) + if err != nil { + return fmt.Errorf("Error adding DHCP pool: %#v", err) + } + + return task.WaitTaskCompletion() + }) + if err != nil { + return fmt.Errorf("Error completing tasks: %#v", err) + } + + } + + d.SetId(d.Get("name").(string)) + + return resourceVcdNetworkRead(d, meta) +} + +func resourceVcdNetworkRead(d *schema.ResourceData, meta interface{}) error { + vcdClient := meta.(*VCDClient) + log.Printf("[DEBUG] VCD Client configuration: %#v", vcdClient) + log.Printf("[DEBUG] VCD Client configuration: %#v", vcdClient.OrgVdc) + + err := vcdClient.OrgVdc.Refresh() + if err != nil { + return fmt.Errorf("Error refreshing vdc: %#v", err) + } + + network, err := vcdClient.OrgVdc.FindVDCNetwork(d.Id()) + if err != nil { + log.Printf("[DEBUG] Network no longer exists. Removing from tfstate") + d.SetId("") + return nil + } + + d.Set("name", network.OrgVDCNetwork.Name) + d.Set("href", network.OrgVDCNetwork.HREF) + if c := network.OrgVDCNetwork.Configuration; c != nil { + d.Set("fence_mode", c.FenceMode) + if c.IPScopes != nil { + d.Set("gateway", c.IPScopes.IPScope.Gateway) + d.Set("netmask", c.IPScopes.IPScope.Netmask) + d.Set("dns1", c.IPScopes.IPScope.DNS1) + d.Set("dns2", c.IPScopes.IPScope.DNS2) + } + } + + return nil +} + +func resourceVcdNetworkDelete(d *schema.ResourceData, meta interface{}) error { + vcdClient := meta.(*VCDClient) + vcdClient.Mutex.Lock() + defer vcdClient.Mutex.Unlock() + err := vcdClient.OrgVdc.Refresh() + if err != nil { + return fmt.Errorf("Error refreshing vdc: %#v", err) + } + + network, err := vcdClient.OrgVdc.FindVDCNetwork(d.Id()) + if err != nil { + return fmt.Errorf("Error finding network: %#v", err) + } + + err = retryCall(vcdClient.MaxRetryTimeout, func() error { + task, err := network.Delete() + if err != nil { + return fmt.Errorf("Error Deleting Network: %#v", err) + } + return task.WaitTaskCompletion() + }) + if err != nil { + return err + } + + return nil +} + +func resourceVcdNetworkIPAddressHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%s-", + strings.ToLower(m["start_address"].(string)))) + buf.WriteString(fmt.Sprintf("%s-", + strings.ToLower(m["end_address"].(string)))) + + return hashcode.String(buf.String()) +} 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 000000000000..fa59d177b7b4 --- /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/hmrc/vmware-govcd" +) + +func TestAccVcdNetwork_Basic(t *testing.T) { + var network govcd.OrgVDCNetwork + generatedHrefRegexp := regexp.MustCompile("^https://") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVcdNetworkDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: fmt.Sprintf(testAccCheckVcdNetwork_basic, os.Getenv("VCD_EDGE_GATWEWAY")), + Check: resource.ComposeTestCheckFunc( + testAccCheckVcdNetworkExists("vcd_network.foonet", &network), + testAccCheckVcdNetworkAttributes(&network), + resource.TestCheckResourceAttr( + "vcd_network.foonet", "name", "foonet"), + resource.TestCheckResourceAttr( + "vcd_network.foonet", "static_ip_pool.#", "1"), + resource.TestCheckResourceAttr( + "vcd_network.foonet", "gateway", "10.10.102.1"), + resource.TestMatchResourceAttr( + "vcd_network.foonet", "href", generatedHrefRegexp), + ), + }, + }, + }) +} + +func testAccCheckVcdNetworkExists(n string, network *govcd.OrgVDCNetwork) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No VAPP ID is set") + } + + conn := testAccProvider.Meta().(*VCDClient) + + resp, err := conn.OrgVdc.FindVDCNetwork(rs.Primary.ID) + if err != nil { + return fmt.Errorf("Network does not exist.") + } + + *network = resp + + return nil + } +} + +func testAccCheckVcdNetworkDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*VCDClient) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "vcd_network" { + continue + } + + _, err := conn.OrgVdc.FindVDCNetwork(rs.Primary.ID) + + if err == nil { + return fmt.Errorf("Network still exists.") + } + + return nil + } + + return nil +} + +func testAccCheckVcdNetworkAttributes(network *govcd.OrgVDCNetwork) resource.TestCheckFunc { + return func(s *terraform.State) error { + + if network.OrgVDCNetwork.Name != "foonet" { + return fmt.Errorf("Bad name: %s", network.OrgVDCNetwork.Name) + } + + return nil + } +} + +const testAccCheckVcdNetwork_basic = ` +resource "vcd_network" "foonet" { + name = "foonet" + edge_gateway = "%s" + gateway = "10.10.102.1" + static_ip_pool { + start_address = "10.10.102.2" + end_address = "10.10.102.254" + } +} +` diff --git a/builtin/providers/vcd/resource_vcd_snat.go b/builtin/providers/vcd/resource_vcd_snat.go new file mode 100644 index 000000000000..c2ae89121065 --- /dev/null +++ b/builtin/providers/vcd/resource_vcd_snat.go @@ -0,0 +1,122 @@ +package vcd + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceVcdSNAT() *schema.Resource { + return &schema.Resource{ + Create: resourceVcdSNATCreate, + Delete: resourceVcdSNATDelete, + Read: resourceVcdSNATRead, + + Schema: map[string]*schema.Schema{ + "edge_gateway": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "external_ip": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "internal_ip": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceVcdSNATCreate(d *schema.ResourceData, meta interface{}) error { + vcdClient := meta.(*VCDClient) + // Multiple VCD components need to run operations on the Edge Gateway, as + // the edge gatway will throw back an error if it is already performing an + // operation we must wait until we can aquire a lock on the client + vcdClient.Mutex.Lock() + defer vcdClient.Mutex.Unlock() + + // Creating a loop to offer further protection from the edge gateway erroring + // due to being busy eg another person is using another client so wouldn't be + // constrained by out lock. If the edge gateway reurns with a busy error, wait + // 3 seconds and then try again. Continue until a non-busy error or success + edgeGateway, err := vcdClient.OrgVdc.FindEdgeGateway(d.Get("edge_gateway").(string)) + if err != nil { + return fmt.Errorf("Unable to find edge gateway: %#v", err) + } + + err = retryCall(vcdClient.MaxRetryTimeout, func() error { + task, err := edgeGateway.AddNATMapping("SNAT", d.Get("internal_ip").(string), + d.Get("external_ip").(string), + "any") + if err != nil { + return fmt.Errorf("Error setting SNAT rules: %#v", err) + } + return task.WaitTaskCompletion() + }) + if err != nil { + return err + } + + d.SetId(d.Get("internal_ip").(string)) + return nil +} + +func resourceVcdSNATRead(d *schema.ResourceData, meta interface{}) error { + vcdClient := meta.(*VCDClient) + e, err := vcdClient.OrgVdc.FindEdgeGateway(d.Get("edge_gateway").(string)) + + if err != nil { + return fmt.Errorf("Unable to find edge gateway: %#v", err) + } + + var found bool + + for _, r := range e.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService.NatRule { + if r.RuleType == "SNAT" && + r.GatewayNatRule.OriginalIP == d.Id() { + found = true + d.Set("external_ip", r.GatewayNatRule.TranslatedIP) + } + } + + if !found { + d.SetId("") + } + + return nil +} + +func resourceVcdSNATDelete(d *schema.ResourceData, meta interface{}) error { + vcdClient := meta.(*VCDClient) + // Multiple VCD components need to run operations on the Edge Gateway, as + // the edge gatway will throw back an error if it is already performing an + // operation we must wait until we can aquire a lock on the client + vcdClient.Mutex.Lock() + defer vcdClient.Mutex.Unlock() + + edgeGateway, err := vcdClient.OrgVdc.FindEdgeGateway(d.Get("edge_gateway").(string)) + if err != nil { + return fmt.Errorf("Unable to find edge gateway: %#v", err) + } + + err = retryCall(vcdClient.MaxRetryTimeout, func() error { + task, err := edgeGateway.RemoveNATMapping("SNAT", d.Get("internal_ip").(string), + d.Get("external_ip").(string), + "") + if err != nil { + return fmt.Errorf("Error setting SNAT rules: %#v", err) + } + return task.WaitTaskCompletion() + }) + if err != nil { + return err + } + + return nil +} 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 000000000000..87c2702a3152 --- /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/hmrc/vmware-govcd" +) + +func TestAccVcdSNAT_Basic(t *testing.T) { + if v := os.Getenv("VCD_EXTERNAL_IP"); v == "" { + t.Skip("Environment variable VCD_EXTERNAL_IP must be set to run SNAT tests") + return + } + + var e govcd.EdgeGateway + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVcdSNATDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: fmt.Sprintf(testAccCheckVcdSnat_basic, os.Getenv("VCD_EDGE_GATWEWAY"), os.Getenv("VCD_EXTERNAL_IP")), + Check: resource.ComposeTestCheckFunc( + testAccCheckVcdSNATExists("vcd_snat.bar", &e), + resource.TestCheckResourceAttr( + "vcd_snat.bar", "external_ip", os.Getenv("VCD_EXTERNAL_IP")), + resource.TestCheckResourceAttr( + "vcd_snat.bar", "internal_ip", "10.10.102.0/24"), + ), + }, + }, + }) +} + +func testAccCheckVcdSNATExists(n string, gateway *govcd.EdgeGateway) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + //return fmt.Errorf("Check this: %#v", rs.Primary) + + if rs.Primary.ID == "" { + return fmt.Errorf("No SNAT ID is set") + } + + conn := testAccProvider.Meta().(*VCDClient) + + gatewayName := rs.Primary.Attributes["edge_gateway"] + edgeGateway, err := conn.OrgVdc.FindEdgeGateway(gatewayName) + + if err != nil { + return fmt.Errorf("Could not find edge gateway") + } + + var found bool + for _, v := range edgeGateway.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService.NatRule { + if v.RuleType == "SNAT" && + v.GatewayNatRule.OriginalIP == "10.10.102.0/24" && + v.GatewayNatRule.OriginalPort == "" && + v.GatewayNatRule.TranslatedIP == os.Getenv("VCD_EXTERNAL_IP") { + found = true + } + } + if !found { + return fmt.Errorf("SNAT rule was not found") + } + + *gateway = edgeGateway + + return nil + } +} + +func testAccCheckVcdSNATDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*VCDClient) + for _, rs := range s.RootModule().Resources { + if rs.Type != "vcd_snat" { + continue + } + + gatewayName := rs.Primary.Attributes["edge_gateway"] + edgeGateway, err := conn.OrgVdc.FindEdgeGateway(gatewayName) + + if err != nil { + return fmt.Errorf("Could not find edge gateway") + } + + var found bool + for _, v := range edgeGateway.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService.NatRule { + if v.RuleType == "SNAT" && + v.GatewayNatRule.OriginalIP == "10.10.102.0/24" && + v.GatewayNatRule.OriginalPort == "" && + v.GatewayNatRule.TranslatedIP == os.Getenv("VCD_EXTERNAL_IP") { + found = true + } + } + + if found { + return fmt.Errorf("SNAT rule still exists.") + } + } + + return nil +} + +const testAccCheckVcdSnat_basic = ` +resource "vcd_snat" "bar" { + edge_gateway = "%s" + external_ip = "%s" + internal_ip = "10.10.102.0/24" +} +` diff --git a/builtin/providers/vcd/resource_vcd_vapp.go b/builtin/providers/vcd/resource_vcd_vapp.go new file mode 100644 index 000000000000..50fc93563f57 --- /dev/null +++ b/builtin/providers/vcd/resource_vcd_vapp.go @@ -0,0 +1,341 @@ +package vcd + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/schema" + types "github.com/hmrc/vmware-govcd/types/v56" + "log" +) + +func resourceVcdVApp() *schema.Resource { + return &schema.Resource{ + Create: resourceVcdVAppCreate, + Update: resourceVcdVAppUpdate, + Read: resourceVcdVAppRead, + Delete: resourceVcdVAppDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "template_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "catalog_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "network_href": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + "network_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "memory": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + }, + "cpus": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + }, + "ip": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "initscript": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "metadata": &schema.Schema{ + Type: schema.TypeMap, + Optional: true, + }, + "href": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "power_on": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + }, + } +} + +func resourceVcdVAppCreate(d *schema.ResourceData, meta interface{}) error { + vcdClient := meta.(*VCDClient) + + catalog, err := vcdClient.Org.FindCatalog(d.Get("catalog_name").(string)) + if err != nil { + return fmt.Errorf("Error finding catalog: %#v", err) + } + + catalogitem, err := catalog.FindCatalogItem(d.Get("template_name").(string)) + if err != nil { + return fmt.Errorf("Error finding catelog item: %#v", err) + } + + vapptemplate, err := catalogitem.GetVAppTemplate() + if err != nil { + return fmt.Errorf("Error finding VAppTemplate: %#v", err) + } + + log.Printf("[DEBUG] VAppTemplate: %#v", vapptemplate) + var networkHref string + net, err := vcdClient.OrgVdc.FindVDCNetwork(d.Get("network_name").(string)) + if err != nil { + return fmt.Errorf("Error finding OrgVCD Network: %#v", err) + } + if attr, ok := d.GetOk("network_href"); ok { + networkHref = attr.(string) + } else { + networkHref = net.OrgVDCNetwork.HREF + } + // vapptemplate := govcd.NewVAppTemplate(&vcdClient.Client) + // + createvapp := &types.InstantiateVAppTemplateParams{ + Ovf: "http://schemas.dmtf.org/ovf/envelope/1", + Xmlns: "http://www.vmware.com/vcloud/v1.5", + Name: d.Get("name").(string), + InstantiationParams: &types.InstantiationParams{ + NetworkConfigSection: &types.NetworkConfigSection{ + Info: "Configuration parameters for logical networks", + NetworkConfig: &types.VAppNetworkConfiguration{ + NetworkName: d.Get("network_name").(string), + Configuration: &types.NetworkConfiguration{ + ParentNetwork: &types.Reference{ + HREF: networkHref, + }, + FenceMode: "bridged", + }, + }, + }, + }, + Source: &types.Reference{ + HREF: vapptemplate.VAppTemplate.HREF, + }, + } + + err = retryCall(vcdClient.MaxRetryTimeout, func() error { + e := vcdClient.OrgVdc.InstantiateVAppTemplate(createvapp) + + if e != nil { + return fmt.Errorf("Error: %#v", e) + } + + e = vcdClient.OrgVdc.Refresh() + if e != nil { + return fmt.Errorf("Error: %#v", e) + } + return nil + }) + if err != nil { + return err + } + + vapp, err := vcdClient.OrgVdc.FindVAppByName(d.Get("name").(string)) + + err = retryCall(vcdClient.MaxRetryTimeout, func() error { + task, err := vapp.ChangeVMName(d.Get("name").(string)) + if err != nil { + return fmt.Errorf("Error with vm name change: %#v", err) + } + + return task.WaitTaskCompletion() + }) + if err != nil { + return fmt.Errorf("Error changing vmname: %#v", err) + } + + err = retryCall(vcdClient.MaxRetryTimeout, func() error { + task, err := vapp.ChangeNetworkConfig(d.Get("network_name").(string), d.Get("ip").(string)) + if err != nil { + return fmt.Errorf("Error with Networking change: %#v", err) + } + return task.WaitTaskCompletion() + }) + if err != nil { + return fmt.Errorf("Error changing network: %#v", err) + } + + if initscript, ok := d.GetOk("initscript"); ok { + err = retryCall(vcdClient.MaxRetryTimeout, func() error { + task, err := vapp.RunCustomizationScript(d.Get("name").(string), initscript.(string)) + if err != nil { + return fmt.Errorf("Error with setting init script: %#v", err) + } + return task.WaitTaskCompletion() + }) + if err != nil { + return fmt.Errorf("Error completing tasks: %#v", err) + } + } + + d.SetId(d.Get("name").(string)) + + return resourceVcdVAppUpdate(d, meta) +} + +func resourceVcdVAppUpdate(d *schema.ResourceData, meta interface{}) error { + vcdClient := meta.(*VCDClient) + vapp, err := vcdClient.OrgVdc.FindVAppByName(d.Id()) + + if err != nil { + return fmt.Errorf("Error finding VApp: %#v", err) + } + + status, err := vapp.GetStatus() + if err != nil { + return fmt.Errorf("Error getting VApp status: %#v", err) + } + + if d.HasChange("metadata") { + oraw, nraw := d.GetChange("metadata") + metadata := oraw.(map[string]interface{}) + for k := range metadata { + task, err := vapp.DeleteMetadata(k) + if err != nil { + return fmt.Errorf("Error deleting metadata: %#v", err) + } + err = task.WaitTaskCompletion() + if err != nil { + return fmt.Errorf("Error completing tasks: %#v", err) + } + } + metadata = nraw.(map[string]interface{}) + for k, v := range metadata { + task, err := vapp.AddMetadata(k, v.(string)) + if err != nil { + return fmt.Errorf("Error adding metadata: %#v", err) + } + err = task.WaitTaskCompletion() + if err != nil { + return fmt.Errorf("Error completing tasks: %#v", err) + } + } + + } + + if d.HasChange("memory") || d.HasChange("cpus") || d.HasChange("power_on") { + if status != "POWERED_OFF" { + task, err := vapp.PowerOff() + if err != nil { + return fmt.Errorf("Error Powering Off: %#v", err) + } + err = task.WaitTaskCompletion() + if err != nil { + return fmt.Errorf("Error completing tasks: %#v", err) + } + } + + if d.HasChange("memory") { + err = retryCall(vcdClient.MaxRetryTimeout, func() error { + task, err := vapp.ChangeMemorySize(d.Get("memory").(int)) + if err != nil { + return fmt.Errorf("Error changing memory size: %#v", err) + } + + return task.WaitTaskCompletion() + }) + if err != nil { + return err + } + } + + if d.HasChange("cpus") { + err = retryCall(vcdClient.MaxRetryTimeout, func() error { + task, err := vapp.ChangeCPUcount(d.Get("cpus").(int)) + if err != nil { + return fmt.Errorf("Error changing cpu count: %#v", err) + } + + return task.WaitTaskCompletion() + }) + if err != nil { + return fmt.Errorf("Error completing task: %#v", err) + } + } + + if d.Get("power_on").(bool) { + task, err := vapp.PowerOn() + if err != nil { + return fmt.Errorf("Error Powering Up: %#v", err) + } + err = task.WaitTaskCompletion() + if err != nil { + return fmt.Errorf("Error completing tasks: %#v", err) + } + } + + } + + return resourceVcdVAppRead(d, meta) +} + +func resourceVcdVAppRead(d *schema.ResourceData, meta interface{}) error { + vcdClient := meta.(*VCDClient) + + err := vcdClient.OrgVdc.Refresh() + if err != nil { + return fmt.Errorf("Error refreshing vdc: %#v", err) + } + + vapp, err := vcdClient.OrgVdc.FindVAppByName(d.Id()) + if err != nil { + log.Printf("[DEBUG] Unable to find vapp. Removing from tfstate") + d.SetId("") + return nil + } + d.Set("ip", vapp.VApp.Children.VM[0].NetworkConnectionSection.NetworkConnection.IPAddress) + + return nil +} + +func resourceVcdVAppDelete(d *schema.ResourceData, meta interface{}) error { + vcdClient := meta.(*VCDClient) + vapp, err := vcdClient.OrgVdc.FindVAppByName(d.Id()) + + if err != nil { + return fmt.Errorf("error finding vapp: %s", err) + } + + if err != nil { + return fmt.Errorf("Error getting VApp status: %#v", err) + } + + _ = retryCall(vcdClient.MaxRetryTimeout, func() error { + task, err := vapp.Undeploy() + if err != nil { + return fmt.Errorf("Error undeploying: %#v", err) + } + + return task.WaitTaskCompletion() + }) + + err = retryCall(vcdClient.MaxRetryTimeout, func() error { + task, err := vapp.Delete() + if err != nil { + return fmt.Errorf("Error deleting: %#v", err) + } + + return task.WaitTaskCompletion() + }) + + return err +} 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 000000000000..38162a64a2cb --- /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/hmrc/vmware-govcd" +) + +func TestAccVcdVApp_PowerOff(t *testing.T) { + var vapp govcd.VApp + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVcdVAppDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: fmt.Sprintf(testAccCheckVcdVApp_basic, os.Getenv("VCD_EDGE_GATWEWAY")), + Check: resource.ComposeTestCheckFunc( + testAccCheckVcdVAppExists("vcd_vapp.foobar", &vapp), + testAccCheckVcdVAppAttributes(&vapp), + resource.TestCheckResourceAttr( + "vcd_vapp.foobar", "name", "foobar"), + resource.TestCheckResourceAttr( + "vcd_vapp.foobar", "ip", "10.10.102.160"), + resource.TestCheckResourceAttr( + "vcd_vapp.foobar", "power_on", "true"), + ), + }, + resource.TestStep{ + Config: fmt.Sprintf(testAccCheckVcdVApp_powerOff, os.Getenv("VCD_EDGE_GATWEWAY")), + Check: resource.ComposeTestCheckFunc( + testAccCheckVcdVAppExists("vcd_vapp.foobar", &vapp), + testAccCheckVcdVAppAttributes_off(&vapp), + resource.TestCheckResourceAttr( + "vcd_vapp.foobar", "name", "foobar"), + resource.TestCheckResourceAttr( + "vcd_vapp.foobar", "ip", "10.10.102.160"), + resource.TestCheckResourceAttr( + "vcd_vapp.foobar", "power_on", "false"), + ), + }, + }, + }) +} + +func testAccCheckVcdVAppExists(n string, vapp *govcd.VApp) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No VAPP ID is set") + } + + conn := testAccProvider.Meta().(*VCDClient) + + resp, err := conn.OrgVdc.FindVAppByName(rs.Primary.ID) + if err != nil { + return err + } + + *vapp = resp + + return nil + } +} + +func testAccCheckVcdVAppDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*VCDClient) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "vcd_vapp" { + continue + } + + _, err := conn.OrgVdc.FindVAppByName(rs.Primary.ID) + + if err == nil { + return fmt.Errorf("VPCs still exist") + } + + return nil + } + + return nil +} + +func testAccCheckVcdVAppAttributes(vapp *govcd.VApp) resource.TestCheckFunc { + return func(s *terraform.State) error { + + if vapp.VApp.Name != "foobar" { + return fmt.Errorf("Bad name: %s", vapp.VApp.Name) + } + + if vapp.VApp.Name != vapp.VApp.Children.VM[0].Name { + return fmt.Errorf("VApp and VM names do not match. %s != %s", + vapp.VApp.Name, vapp.VApp.Children.VM[0].Name) + } + + status, _ := vapp.GetStatus() + if status != "POWERED_ON" { + return fmt.Errorf("VApp is not powered on") + } + + return nil + } +} + +func testAccCheckVcdVAppAttributes_off(vapp *govcd.VApp) resource.TestCheckFunc { + return func(s *terraform.State) error { + + if vapp.VApp.Name != "foobar" { + return fmt.Errorf("Bad name: %s", vapp.VApp.Name) + } + + if vapp.VApp.Name != vapp.VApp.Children.VM[0].Name { + return fmt.Errorf("VApp and VM names do not match. %s != %s", + vapp.VApp.Name, vapp.VApp.Children.VM[0].Name) + } + + status, _ := vapp.GetStatus() + if status != "POWERED_OFF" { + return fmt.Errorf("VApp is still powered on") + } + + return nil + } +} + +const testAccCheckVcdVApp_basic = ` +resource "vcd_network" "foonet" { + name = "foonet" + edge_gateway = "%s" + gateway = "10.10.102.1" + static_ip_pool { + start_address = "10.10.102.2" + end_address = "10.10.102.254" + } +} + +resource "vcd_vapp" "foobar" { + name = "foobar" + template_name = "base-centos-7.0-x86_64_v-0.1_b-74" + catalog_name = "NubesLab" + network_name = "${vcd_network.foonet.name}" + memory = 1024 + cpus = 1 + ip = "10.10.102.160" +} +` + +const testAccCheckVcdVApp_powerOff = ` +resource "vcd_network" "foonet" { + name = "foonet" + edge_gateway = "%s" + gateway = "10.10.102.1" + static_ip_pool { + start_address = "10.10.102.2" + end_address = "10.10.102.254" + } +} + +resource "vcd_vapp" "foobar" { + name = "foobar" + template_name = "base-centos-7.0-x86_64_v-0.1_b-74" + catalog_name = "NubesLab" + network_name = "${vcd_network.foonet.name}" + memory = 1024 + cpus = 1 + ip = "10.10.102.160" + power_on = false +} +` diff --git a/builtin/providers/vcd/structure.go b/builtin/providers/vcd/structure.go new file mode 100644 index 000000000000..d4ac65eaeea0 --- /dev/null +++ b/builtin/providers/vcd/structure.go @@ -0,0 +1,112 @@ +package vcd + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + types "github.com/hmrc/vmware-govcd/types/v56" + "strconv" + "time" +) + +func expandIPRange(configured []interface{}) types.IPRanges { + ipRange := make([]*types.IPRange, 0, len(configured)) + + for _, ipRaw := range configured { + data := ipRaw.(map[string]interface{}) + + ip := types.IPRange{ + StartAddress: data["start_address"].(string), + EndAddress: data["end_address"].(string), + } + + ipRange = append(ipRange, &ip) + } + + ipRanges := types.IPRanges{ + IPRange: ipRange, + } + + return ipRanges +} + +func expandFirewallRules(d *schema.ResourceData, gateway *types.EdgeGateway) ([]*types.FirewallRule, error) { + //firewallRules := make([]*types.FirewallRule, 0, len(configured)) + firewallRules := gateway.Configuration.EdgeGatewayServiceConfiguration.FirewallService.FirewallRule + + rulesCount := d.Get("rule.#").(int) + for i := 0; i < rulesCount; i++ { + prefix := fmt.Sprintf("rule.%d", i) + + var protocol *types.FirewallRuleProtocols + switch d.Get(prefix + ".protocol").(string) { + case "tcp": + protocol = &types.FirewallRuleProtocols{ + TCP: true, + } + case "udp": + protocol = &types.FirewallRuleProtocols{ + UDP: true, + } + case "icmp": + protocol = &types.FirewallRuleProtocols{ + ICMP: true, + } + default: + protocol = &types.FirewallRuleProtocols{ + Any: true, + } + } + rule := &types.FirewallRule{ + //ID: strconv.Itoa(len(configured) - i), + IsEnabled: true, + MatchOnTranslate: false, + Description: d.Get(prefix + ".description").(string), + Policy: d.Get(prefix + ".policy").(string), + Protocols: protocol, + Port: getNumericPort(d.Get(prefix + ".destination_port")), + DestinationPortRange: d.Get(prefix + ".destination_port").(string), + DestinationIP: d.Get(prefix + ".destination_ip").(string), + SourcePort: getNumericPort(d.Get(prefix + ".source_port")), + SourcePortRange: d.Get(prefix + ".source_port").(string), + SourceIP: d.Get(prefix + ".source_ip").(string), + EnableLogging: false, + } + firewallRules = append(firewallRules, rule) + } + + return firewallRules, nil +} + +func getProtocol(protocol types.FirewallRuleProtocols) string { + if protocol.TCP { + return "tcp" + } + if protocol.UDP { + return "udp" + } + if protocol.ICMP { + return "icmp" + } + return "any" +} + +func getNumericPort(portrange interface{}) int { + i, err := strconv.Atoi(portrange.(string)) + if err != nil { + return -1 + } + return i +} + +func getPortString(port int) string { + if port == -1 { + return "any" + } + portstring := strconv.Itoa(port) + return portstring +} + +func retryCall(seconds int, f resource.RetryFunc) error { + return resource.Retry(time.Duration(seconds)*time.Second, f) +} diff --git a/website/source/assets/stylesheets/_docs.scss b/website/source/assets/stylesheets/_docs.scss index 11dff7720e7b..7805a2f9a4ae 100755 --- a/website/source/assets/stylesheets/_docs.scss +++ b/website/source/assets/stylesheets/_docs.scss @@ -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, 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 000000000000..d4d5e9d698d0 --- /dev/null +++ b/website/source/docs/providers/vcd/index.html.markdown @@ -0,0 +1,58 @@ +--- +layout: "vcd" +page_title: "Provider: VMware vCloudDirector" +sidebar_current: "docs-vcd-index" +description: |- + The VMware vCloud Director provider is used to interact with the resources supported by VMware vCloud Director. The provider needs to be configured with the proper credentials before it can be used. +--- + +# VMware vCloud Director Provider + +The VMware vCloud Director provider is used to interact with the resources supported by VMware vCloud Director. The provider needs to be configured with the proper credentials before it can be used. + +Use the navigation to the left to read about the available resources. + +~> **NOTE:** The VMware vCloud Director Provider currently represents _initial support_ and therefore may undergo significant changes as the community improves it. + +## Example Usage + +``` +# Configure the VMware vCloud Director Provider +provider "vcd" { + user = "${var.vcd_user}" + password = "${var.vcd_pass}" + org = "${var.vcd_org}" + url = "${var.vcd_url}" + vdc = "${var.vcd_vdc}" + maxRetryTimeout = "${var.vcd_maxRetryTimeout}" +} + +# Create a new network +resource "vcd_network" "net" { + ... +} +``` + +## Argument Reference + +The following arguments are used to configure the VMware vCloud Director Provider: + +* `user` - (Required) This is the username for vCloud Director API operations. Can also + be specified with the `VCD_USER` environment variable. +* `password` - (Required) This is the password for vCloud Director API operations. Can + also be specified with the `VCD_PASSWORD` environment variable. +* `org` - (Required) This is the vCloud Director Org on which to run API + operations. Can also be specified with the `VCD_ORG` environment + variable. +* `url` - (Required) This is the URL for the vCloud Director API. + Can also be specified with the `VCD_URL` environment variable. +* `vdc` - (Optional) This is the virtual datacenter within vCloud Director to run + API operations against. If not set the plugin will select the first virtual + datacenter available to your Org. Can also be specified with the `VCD_VDC` environment + variable. +* `maxRetryTimeout` - (Optional) This provides you with the ability to specify the maximum + amount of time (in seconds) you are prepared to wait for interactions on resources managed + by vCloud Director to be successful. If a resource action fails, the action will be retried + (as long as it is still within the `maxRetryTimeout` value) to try and ensure success. + Defaults to 60 seconds if not set. + Can also be specified with the `VCD_MAX_RETRY_TIMEOUT` environment variable. 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 000000000000..dd6fb92b0a38 --- /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 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 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 000000000000..172237322ac8 --- /dev/null +++ b/website/source/docs/providers/vcd/r/firewall_rules.html.markdown @@ -0,0 +1,83 @@ +--- +layout: "vcd" +page_title: "vCloudDirector: vcd_firewall_rules" +sidebar_current: "docs-vcd-resource-firewall-rules" +description: |- + Provides a vCloud Director Firewall resource. This can be used to create, modify, and delete firewall settings and rules. +--- + +# vcd\_firewall\_rules + +Provides a vCloud Director Firewall resource. This can be used to create, +modify, and delete firewall settings and rules. + +## Example Usage + +``` +resource "vcd_firewall_rules" "fw" { + edge_gateway = "Edge Gateway Name" + default_action = "drop" + + rule { + description = "deny-ftp-out" + policy = "deny" + protocol = "tcp" + destination_port = "21" + destination_ip = "any" + source_port = "any" + source_ip = "10.10.0.0/24" + } + + rule { + description = "allow-outbound" + policy = "allow" + protocol = "any" + destination_port = "any" + destination_ip = "any" + source_port = "any" + source_ip = "10.10.0.0/24" + } + +} + +resource "vcd_vapp" "web" { + ... +} + +resource "vcd_firewall_rules" "fw-web" { + edge_gateway = "Edge Gateway Name" + default_action = "drop" + + rule { + description = "allow-web" + policy = "allow" + protocol = "tcp" + destination_port = "80" + destination_ip = "${vcd_vapp.web.ip}" + source_port = "any" + source_ip = "any" + } +} + +``` + +## Argument Reference + +The following arguments are supported: + +* `edge_gateway` - (Required) The name of the edge gateway on which to apply the Firewall Rules +* `default_action` - (Required) Either "allow" or "deny". Specifies what to do should none of the rules match +* `rule` - (Optional) Configures a firewall rule; see [Rules](#rules) below for details. + + +## 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 000000000000..eead8c58ea4c --- /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 000000000000..dc8b567c7c3f --- /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 000000000000..0a2a2e234eba --- /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 59eb10df9e49..462ec0b4f10d 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -193,6 +193,10 @@ TLS + > + VMware vCloud Director + + > VMware vSphere diff --git a/website/source/layouts/vcd.erb b/website/source/layouts/vcd.erb new file mode 100644 index 000000000000..8bafe264973d --- /dev/null +++ b/website/source/layouts/vcd.erb @@ -0,0 +1,38 @@ +<% wrap_layout :inner do %> + <% content_for :sidebar do %> + + <% end %> + + <%= yield %> +<% end %>