diff --git a/builtin/providers/cloudstack/config.go b/builtin/providers/cloudstack/config.go index 9185bb27e7..8a2ad813b2 100644 --- a/builtin/providers/cloudstack/config.go +++ b/builtin/providers/cloudstack/config.go @@ -5,15 +5,17 @@ import "github.com/xanzy/go-cloudstack/cloudstack" // Config is the configuration structure used to instantiate a // new CloudStack client. type Config struct { - ApiURL string - ApiKey string - SecretKey string - Timeout int64 + APIURL string + APIKey string + SecretKey string + HTTPGETOnly bool + Timeout int64 } -// Client() returns a new CloudStack client. +// NewClient returns a new CloudStack client. func (c *Config) NewClient() (*cloudstack.CloudStackClient, error) { - cs := cloudstack.NewAsyncClient(c.ApiURL, c.ApiKey, c.SecretKey, false) + cs := cloudstack.NewAsyncClient(c.APIURL, c.APIKey, c.SecretKey, false) + cs.HTTPGETOnly = c.HTTPGETOnly cs.AsyncTimeout(c.Timeout) return cs, nil } diff --git a/builtin/providers/cloudstack/provider.go b/builtin/providers/cloudstack/provider.go index 73ce38b764..740c2d17a6 100644 --- a/builtin/providers/cloudstack/provider.go +++ b/builtin/providers/cloudstack/provider.go @@ -27,6 +27,12 @@ func Provider() terraform.ResourceProvider { DefaultFunc: schema.EnvDefaultFunc("CLOUDSTACK_SECRET_KEY", nil), }, + "http_get_only": &schema.Schema{ + Type: schema.TypeBool, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("CLOUDSTACK_HTTP_GET_ONLY", false), + }, + "timeout": &schema.Schema{ Type: schema.TypeInt, Required: true, @@ -45,6 +51,7 @@ func Provider() terraform.ResourceProvider { "cloudstack_network_acl_rule": resourceCloudStackNetworkACLRule(), "cloudstack_nic": resourceCloudStackNIC(), "cloudstack_port_forward": resourceCloudStackPortForward(), + "cloudstack_secondary_ipaddress": resourceCloudStackSecondaryIPAddress(), "cloudstack_ssh_keypair": resourceCloudStackSSHKeyPair(), "cloudstack_template": resourceCloudStackTemplate(), "cloudstack_vpc": resourceCloudStackVPC(), @@ -59,10 +66,11 @@ func Provider() terraform.ResourceProvider { func providerConfigure(d *schema.ResourceData) (interface{}, error) { config := Config{ - ApiURL: d.Get("api_url").(string), - ApiKey: d.Get("api_key").(string), - SecretKey: d.Get("secret_key").(string), - Timeout: int64(d.Get("timeout").(int)), + APIURL: d.Get("api_url").(string), + APIKey: d.Get("api_key").(string), + SecretKey: d.Get("secret_key").(string), + HTTPGETOnly: d.Get("http_get_only").(bool), + Timeout: int64(d.Get("timeout").(int)), } return config.NewClient() diff --git a/builtin/providers/cloudstack/resource_cloudstack_disk.go b/builtin/providers/cloudstack/resource_cloudstack_disk.go index c4d84f520d..1c7ad36bd2 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_disk.go +++ b/builtin/providers/cloudstack/resource_cloudstack_disk.go @@ -165,7 +165,7 @@ func resourceCloudStackDiskRead(d *schema.ResourceData, meta interface{}) error } d.Set("device", retrieveDeviceName(v.Deviceid, c.Name)) - d.Set("virtual_machine", v.Vmname) + setValueOrUUID(d, "virtual_machine", v.Vmname, v.Virtualmachineid) } return nil diff --git a/builtin/providers/cloudstack/resource_cloudstack_instance.go b/builtin/providers/cloudstack/resource_cloudstack_instance.go index 2c434f53cf..106b87e11d 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_instance.go +++ b/builtin/providers/cloudstack/resource_cloudstack_instance.go @@ -106,8 +106,14 @@ func resourceCloudStackInstanceCreate(d *schema.ResourceData, meta interface{}) return e.Error() } + // Retrieve the zone UUID + zoneid, e := retrieveUUID(cs, "zone", d.Get("zone").(string)) + if e != nil { + return e.Error() + } + // Retrieve the zone object - zone, _, err := cs.Zone.GetZoneByName(d.Get("zone").(string)) + zone, _, err := cs.Zone.GetZoneByID(zoneid) if err != nil { return err } @@ -168,12 +174,18 @@ func resourceCloudStackInstanceCreate(d *schema.ResourceData, meta interface{}) if userData, ok := d.GetOk("user_data"); ok { ud := base64.StdEncoding.EncodeToString([]byte(userData.(string))) - // deployVirtualMachine uses POST, so max userdata is 32K - // https://github.com/xanzy/go-cloudstack/commit/c767de689df1faedfec69233763a7c5334bee1f6 - if len(ud) > 32768 { + // deployVirtualMachine uses POST by default, so max userdata is 32K + maxUD := 32768 + + if cs.HTTPGETOnly { + // deployVirtualMachine using GET instead, so max userdata is 2K + maxUD = 2048 + } + + if len(ud) > maxUD { return fmt.Errorf( "The supplied user_data contains %d bytes after encoding, "+ - "this exeeds the limit of 32768 bytes", len(ud)) + "this exeeds the limit of %d bytes", len(ud), maxUD) } p.SetUserdata(ud) @@ -204,7 +216,6 @@ func resourceCloudStackInstanceRead(d *schema.ResourceData, meta interface{}) er if err != nil { if count == 0 { log.Printf("[DEBUG] Instance %s does no longer exist", d.Get("name").(string)) - // Clear out all details so it's obvious the instance is gone d.SetId("") return nil } @@ -216,13 +227,13 @@ func resourceCloudStackInstanceRead(d *schema.ResourceData, meta interface{}) er d.Set("name", vm.Name) d.Set("display_name", vm.Displayname) d.Set("ipaddress", vm.Nic[0].Ipaddress) - d.Set("zone", vm.Zonename) //NB cloudstack sometimes sends back the wrong keypair name, so dont update it setValueOrUUID(d, "network", vm.Nic[0].Networkname, vm.Nic[0].Networkid) setValueOrUUID(d, "service_offering", vm.Serviceofferingname, vm.Serviceofferingid) setValueOrUUID(d, "template", vm.Templatename, vm.Templateid) setValueOrUUID(d, "project", vm.Project, vm.Projectid) + setValueOrUUID(d, "zone", vm.Zonename, vm.Zoneid) return nil } @@ -256,7 +267,8 @@ func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{}) // Attributes that require reboot to update if d.HasChange("service_offering") || d.HasChange("keypair") { // Before we can actually make these changes, the virtual machine must be stopped - _, err := cs.VirtualMachine.StopVirtualMachine(cs.VirtualMachine.NewStopVirtualMachineParams(d.Id())) + _, err := cs.VirtualMachine.StopVirtualMachine( + cs.VirtualMachine.NewStopVirtualMachineParams(d.Id())) if err != nil { return fmt.Errorf( "Error stopping instance %s before making changes: %s", name, err) @@ -299,12 +311,14 @@ func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{}) } // Start the virtual machine again - _, err = cs.VirtualMachine.StartVirtualMachine(cs.VirtualMachine.NewStartVirtualMachineParams(d.Id())) + _, err = cs.VirtualMachine.StartVirtualMachine( + cs.VirtualMachine.NewStartVirtualMachineParams(d.Id())) if err != nil { return fmt.Errorf( "Error starting instance %s after making changes", name) } } + d.Partial(false) return resourceCloudStackInstanceRead(d, meta) } diff --git a/builtin/providers/cloudstack/resource_cloudstack_ipaddress.go b/builtin/providers/cloudstack/resource_cloudstack_ipaddress.go index ea2e440b0f..84e067f81e 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_ipaddress.go +++ b/builtin/providers/cloudstack/resource_cloudstack_ipaddress.go @@ -105,7 +105,7 @@ func resourceCloudStackIPAddressRead(d *schema.ResourceData, meta interface{}) e return err } - d.Set("network", n.Name) + setValueOrUUID(d, "network", n.Name, f.Associatednetworkid) } if _, ok := d.GetOk("vpc"); ok { @@ -115,7 +115,7 @@ func resourceCloudStackIPAddressRead(d *schema.ResourceData, meta interface{}) e return err } - d.Set("vpc", v.Name) + setValueOrUUID(d, "vpc", v.Name, f.Vpcid) } return nil diff --git a/builtin/providers/cloudstack/resource_cloudstack_network_acl_rule.go b/builtin/providers/cloudstack/resource_cloudstack_network_acl_rule.go index 6900ef1686..d4ccf49d25 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_network_acl_rule.go +++ b/builtin/providers/cloudstack/resource_cloudstack_network_acl_rule.go @@ -157,21 +157,23 @@ func resourceCloudStackNetworkACLRuleCreateRule( p.SetIcmptype(rule["icmp_type"].(int)) p.SetIcmpcode(rule["icmp_code"].(int)) - r, err := cs.NetworkACL.CreateNetworkACL(p) + r, err := Retry(4, retryableACLCreationFunc(cs, p)) if err != nil { return err } - uuids["icmp"] = r.Id + + uuids["icmp"] = r.(*cloudstack.CreateNetworkACLResponse).Id rule["uuids"] = uuids } // If the protocol is ALL set the needed parameters if rule["protocol"].(string) == "all" { - r, err := cs.NetworkACL.CreateNetworkACL(p) + r, err := Retry(4, retryableACLCreationFunc(cs, p)) if err != nil { return err } - uuids["all"] = r.Id + + uuids["all"] = r.(*cloudstack.CreateNetworkACLResponse).Id rule["uuids"] = uuids } @@ -206,7 +208,7 @@ func resourceCloudStackNetworkACLRuleCreateRule( p.SetStartport(startPort) p.SetEndport(endPort) - r, err := cs.NetworkACL.CreateNetworkACL(p) + r, err := Retry(4, retryableACLCreationFunc(cs, p)) if err != nil { return err } @@ -214,7 +216,7 @@ func resourceCloudStackNetworkACLRuleCreateRule( ports.Add(port) rule["ports"] = ports - uuids[port.(string)] = r.Id + uuids[port.(string)] = r.(*cloudstack.CreateNetworkACLResponse).Id rule["uuids"] = uuids } } @@ -593,3 +595,15 @@ func verifyNetworkACLRuleParams(d *schema.ResourceData, rule map[string]interfac return nil } + +func retryableACLCreationFunc( + cs *cloudstack.CloudStackClient, + p *cloudstack.CreateNetworkACLParams) func() (interface{}, error) { + return func() (interface{}, error) { + r, err := cs.NetworkACL.CreateNetworkACL(p) + if err != nil { + return nil, err + } + return r, nil + } +} diff --git a/builtin/providers/cloudstack/resource_cloudstack_port_forward.go b/builtin/providers/cloudstack/resource_cloudstack_port_forward.go index ebb74b3517..4982356d67 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_port_forward.go +++ b/builtin/providers/cloudstack/resource_cloudstack_port_forward.go @@ -116,7 +116,12 @@ func resourceCloudStackPortForwardCreateForward( } // Retrieve the virtual_machine UUID - vm, _, err := cs.VirtualMachine.GetVirtualMachineByName(forward["virtual_machine"].(string)) + virtualmachineid, e := retrieveUUID(cs, "virtual_machine", forward["virtual_machine"].(string)) + if e != nil { + return e.Error() + } + + vm, _, err := cs.VirtualMachine.GetVirtualMachineByID(virtualmachineid) if err != nil { return err } @@ -186,7 +191,13 @@ func resourceCloudStackPortForwardRead(d *schema.ResourceData, meta interface{}) forward["protocol"] = r.Protocol forward["private_port"] = privPort forward["public_port"] = pubPort - forward["virtual_machine"] = r.Virtualmachinename + + if isUUID(forward["virtual_machine"].(string)) { + forward["virtual_machine"] = r.Virtualmachineid + } else { + forward["virtual_machine"] = r.Virtualmachinename + } + forwards.Add(forward) } } @@ -219,7 +230,7 @@ func resourceCloudStackPortForwardRead(d *schema.ResourceData, meta interface{}) } } - for uuid, _ := range uuids { + for uuid := range uuids { // Make a dummy forward to hold the unknown UUID forward := map[string]interface{}{ "protocol": "N/A", diff --git a/builtin/providers/cloudstack/resource_cloudstack_secondary_ipaddress.go b/builtin/providers/cloudstack/resource_cloudstack_secondary_ipaddress.go new file mode 100644 index 0000000000..1c491be449 --- /dev/null +++ b/builtin/providers/cloudstack/resource_cloudstack_secondary_ipaddress.go @@ -0,0 +1,160 @@ +package cloudstack + +import ( + "fmt" + "log" + "strings" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/xanzy/go-cloudstack/cloudstack" +) + +func resourceCloudStackSecondaryIPAddress() *schema.Resource { + return &schema.Resource{ + Create: resourceCloudStackSecondaryIPAddressCreate, + Read: resourceCloudStackSecondaryIPAddressRead, + Delete: resourceCloudStackSecondaryIPAddressDelete, + + Schema: map[string]*schema.Schema{ + "ipaddress": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "nicid": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "virtual_machine": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceCloudStackSecondaryIPAddressCreate(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + nicid := d.Get("nicid").(string) + if nicid == "" { + // Retrieve the virtual_machine UUID + virtualmachineid, e := retrieveUUID(cs, "virtual_machine", d.Get("virtual_machine").(string)) + if e != nil { + return e.Error() + } + + // Get the virtual machine details + vm, count, err := cs.VirtualMachine.GetVirtualMachineByID(virtualmachineid) + if err != nil { + if count == 0 { + log.Printf("[DEBUG] Instance %s does no longer exist", d.Get("virtual_machine").(string)) + d.SetId("") + return nil + } + return err + } + + nicid = vm.Nic[0].Id + } + + // Create a new parameter struct + p := cs.Nic.NewAddIpToNicParams(nicid) + + if addr := d.Get("ipaddress").(string); addr != "" { + p.SetIpaddress(addr) + } + + ip, err := cs.Nic.AddIpToNic(p) + if err != nil { + return err + } + + d.SetId(ip.Id) + + return nil +} + +func resourceCloudStackSecondaryIPAddressRead(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + // Retrieve the virtual_machine UUID + virtualmachineid, e := retrieveUUID(cs, "virtual_machine", d.Get("virtual_machine").(string)) + if e != nil { + return e.Error() + } + + // Get the virtual machine details + vm, count, err := cs.VirtualMachine.GetVirtualMachineByID(virtualmachineid) + if err != nil { + if count == 0 { + log.Printf("[DEBUG] Instance %s does no longer exist", d.Get("virtual_machine").(string)) + d.SetId("") + return nil + } + return err + } + + nicid := d.Get("nicid").(string) + if nicid == "" { + nicid = vm.Nic[0].Id + } + + p := cs.Nic.NewListNicsParams(virtualmachineid) + p.SetNicid(nicid) + + l, err := cs.Nic.ListNics(p) + if err != nil { + return err + } + + if l.Count == 0 { + log.Printf("[DEBUG] NIC %s does no longer exist", d.Get("nicid").(string)) + d.SetId("") + return nil + } + + if l.Count > 1 { + return fmt.Errorf("Found more then one possible result: %v", l.Nics) + } + + for _, ip := range l.Nics[0].Secondaryip { + if ip.Id == d.Id() { + d.Set("ipaddress", ip.Ipaddress) + d.Set("nicid", l.Nics[0].Id) + return nil + } + } + + log.Printf("[DEBUG] IP %s no longer exist", d.Get("ipaddress").(string)) + d.SetId("") + + return nil +} + +func resourceCloudStackSecondaryIPAddressDelete(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + // Create a new parameter struct + p := cs.Nic.NewRemoveIpFromNicParams(d.Id()) + + log.Printf("[INFO] Removing secondary IP address: %s", d.Get("ipaddress").(string)) + if _, err := cs.Nic.RemoveIpFromNic(p); err != nil { + // This is a very poor way to be told the UUID does no longer exist :( + if strings.Contains(err.Error(), fmt.Sprintf( + "Invalid parameter id value=%s due to incorrect long value format, "+ + "or entity does not exist", d.Id())) { + return nil + } + + return fmt.Errorf("Error removing secondary IP address: %s", err) + } + + return nil +} diff --git a/builtin/providers/cloudstack/resource_cloudstack_secondary_ipaddress_test.go b/builtin/providers/cloudstack/resource_cloudstack_secondary_ipaddress_test.go new file mode 100644 index 0000000000..e0c353e208 --- /dev/null +++ b/builtin/providers/cloudstack/resource_cloudstack_secondary_ipaddress_test.go @@ -0,0 +1,225 @@ +package cloudstack + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/xanzy/go-cloudstack/cloudstack" +) + +func TestAccCloudStackSecondaryIPAddress_basic(t *testing.T) { + var ip cloudstack.AddIpToNicResponse + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackSecondaryIPAddressDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCloudStackSecondaryIPAddress_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackSecondaryIPAddressExists( + "cloudstack_secondary_ipaddress.foo", &ip), + ), + }, + }, + }) +} + +func TestAccCloudStackSecondaryIPAddress_fixedIP(t *testing.T) { + var ip cloudstack.AddIpToNicResponse + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackSecondaryIPAddressDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCloudStackSecondaryIPAddress_fixedIP, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackSecondaryIPAddressExists( + "cloudstack_secondary_ipaddress.foo", &ip), + testAccCheckCloudStackSecondaryIPAddressAttributes(&ip), + resource.TestCheckResourceAttr( + "cloudstack_secondary_ipaddress.foo", "ipaddress", CLOUDSTACK_NETWORK_1_IPADDRESS), + ), + }, + }, + }) +} + +func testAccCheckCloudStackSecondaryIPAddressExists( + n string, ip *cloudstack.AddIpToNicResponse) 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 IP address ID is set") + } + + cs := testAccProvider.Meta().(*cloudstack.CloudStackClient) + + // Retrieve the virtual_machine UUID + virtualmachineid, e := retrieveUUID( + cs, "virtual_machine", rs.Primary.Attributes["virtual_machine"]) + if e != nil { + return e.Error() + } + + // Get the virtual machine details + vm, count, err := cs.VirtualMachine.GetVirtualMachineByID(virtualmachineid) + if err != nil { + if count == 0 { + return fmt.Errorf("Instance not found") + } + return err + } + + nicid := rs.Primary.Attributes["nicid"] + if nicid == "" { + nicid = vm.Nic[0].Id + } + + p := cs.Nic.NewListNicsParams(virtualmachineid) + p.SetNicid(nicid) + + l, err := cs.Nic.ListNics(p) + if err != nil { + return err + } + + if l.Count == 0 { + return fmt.Errorf("NIC not found") + } + + if l.Count > 1 { + return fmt.Errorf("Found more then one possible result: %v", l.Nics) + } + + for _, sip := range l.Nics[0].Secondaryip { + if sip.Id == rs.Primary.ID { + ip.Ipaddress = sip.Ipaddress + ip.Nicid = l.Nics[0].Id + return nil + } + } + + return fmt.Errorf("IP address not found") + } +} + +func testAccCheckCloudStackSecondaryIPAddressAttributes( + ip *cloudstack.AddIpToNicResponse) resource.TestCheckFunc { + return func(s *terraform.State) error { + + if ip.Ipaddress != CLOUDSTACK_NETWORK_1_IPADDRESS { + return fmt.Errorf("Bad IP address: %s", ip.Ipaddress) + } + return nil + } +} + +func testAccCheckCloudStackSecondaryIPAddressDestroy(s *terraform.State) error { + cs := testAccProvider.Meta().(*cloudstack.CloudStackClient) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "cloudstack_secondary_ipaddress" { + continue + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No IP address ID is set") + } + + // Retrieve the virtual_machine UUID + virtualmachineid, e := retrieveUUID( + cs, "virtual_machine", rs.Primary.Attributes["virtual_machine"]) + if e != nil { + return e.Error() + } + + // Get the virtual machine details + vm, count, err := cs.VirtualMachine.GetVirtualMachineByID(virtualmachineid) + if err != nil { + if count == 0 { + return fmt.Errorf("Instance not found") + } + return err + } + + nicid := rs.Primary.Attributes["nicid"] + if nicid == "" { + nicid = vm.Nic[0].Id + } + + p := cs.Nic.NewListNicsParams(virtualmachineid) + p.SetNicid(nicid) + + l, err := cs.Nic.ListNics(p) + if err != nil { + return err + } + + if l.Count == 0 { + return fmt.Errorf("NIC not found") + } + + if l.Count > 1 { + return fmt.Errorf("Found more then one possible result: %v", l.Nics) + } + + for _, sip := range l.Nics[0].Secondaryip { + if sip.Id == rs.Primary.ID { + return fmt.Errorf("IP address %s still exists", rs.Primary.ID) + } + } + + return nil + } + + return nil +} + +var testAccCloudStackSecondaryIPAddress_basic = fmt.Sprintf(` +resource "cloudstack_instance" "foobar" { + name = "terraform-test" + service_offering= "%s" + network = "%s" + template = "%s" + zone = "%s" + expunge = true +} + +resource "cloudstack_secondary_ipaddress" "foo" { + virtual_machine = "${cloudstack_instance.foobar.id}" +} +`, + CLOUDSTACK_SERVICE_OFFERING_1, + CLOUDSTACK_NETWORK_1, + CLOUDSTACK_TEMPLATE, + CLOUDSTACK_ZONE) + +var testAccCloudStackSecondaryIPAddress_fixedIP = fmt.Sprintf(` +resource "cloudstack_instance" "foobar" { + name = "terraform-test" + service_offering= "%s" + network = "%s" + template = "%s" + zone = "%s" + expunge = true +} + +resource "cloudstack_secondary_ipaddress" "foo" { + ipaddress = "%s" + virtual_machine = "${cloudstack_instance.foobar.id}" +}`, + CLOUDSTACK_SERVICE_OFFERING_1, + CLOUDSTACK_NETWORK_1, + CLOUDSTACK_TEMPLATE, + CLOUDSTACK_ZONE, + CLOUDSTACK_NETWORK_1_IPADDRESS) diff --git a/builtin/providers/cloudstack/resources.go b/builtin/providers/cloudstack/resources.go index 5d0e5eb553..cc826492f8 100644 --- a/builtin/providers/cloudstack/resources.go +++ b/builtin/providers/cloudstack/resources.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "regexp" + "time" "github.com/hashicorp/terraform/helper/schema" "github.com/xanzy/go-cloudstack/cloudstack" @@ -113,3 +114,24 @@ func isUUID(s string) bool { re := regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`) return re.MatchString(s) } + +// RetryFunc is the function retried n times +type RetryFunc func() (interface{}, error) + +// Retry is a wrapper around a RetryFunc that will retry a function +// n times or until it succeeds. +func Retry(n int, f RetryFunc) (interface{}, error) { + var lastErr error + + for i := 0; i < n; i++ { + r, err := f() + if err == nil { + return r, nil + } + + lastErr = err + time.Sleep(30 * time.Second) + } + + return nil, lastErr +} diff --git a/website/source/docs/providers/cloudstack/index.html.markdown b/website/source/docs/providers/cloudstack/index.html.markdown index 0738370b61..4d0936e76d 100644 --- a/website/source/docs/providers/cloudstack/index.html.markdown +++ b/website/source/docs/providers/cloudstack/index.html.markdown @@ -44,7 +44,12 @@ The following arguments are supported: * `secret_key` - (Required) This is the CloudStack secret key. It must be provided, but it can also be sourced from the `CLOUDSTACK_SECRET_KEY` environment variable. -* `timeout` - (Optional) A value in seconds. This is the time allowed for Cloudstack +* `http_get_only` - (Optional) Some cloud providers only allow HTTP GET calls to + their CloudStack API. If using such a provider, you need to set this to `true` + in order for the provider to only make GET calls and no POST calls. It can also + be sourced from the `CLOUDSTACK_HTTP_GET_ONLY` environment variable. + +* `timeout` - (Optional) A value in seconds. This is the time allowed for Cloudstack to complete each asynchronous job triggered. If unset, this can be sourced from the - `CLOUDSTACK_TIMEOUT` environment variable. Otherwise, this will default to 300 + `CLOUDSTACK_TIMEOUT` environment variable. Otherwise, this will default to 300 seconds. diff --git a/website/source/docs/providers/cloudstack/r/disk.html.markdown b/website/source/docs/providers/cloudstack/r/disk.html.markdown index 53ae73656e..2dbd40ee6f 100644 --- a/website/source/docs/providers/cloudstack/r/disk.html.markdown +++ b/website/source/docs/providers/cloudstack/r/disk.html.markdown @@ -44,7 +44,7 @@ The following arguments are supported: * `shrink_ok` - (Optional) Verifies if the disk volume is allowed to shrink when resizing (defaults false). -* `virtual_machine` - (Optional) The name of the virtual machine to which you +* `virtual_machine` - (Optional) The name or ID of the virtual machine to which you want to attach the disk volume. * `zone` - (Required) The name or ID of the zone where this disk volume will be available. diff --git a/website/source/docs/providers/cloudstack/r/firewall.html.markdown b/website/source/docs/providers/cloudstack/r/firewall.html.markdown index 8b8aa0089c..455449049d 100644 --- a/website/source/docs/providers/cloudstack/r/firewall.html.markdown +++ b/website/source/docs/providers/cloudstack/r/firewall.html.markdown @@ -28,8 +28,8 @@ resource "cloudstack_firewall" "default" { The following arguments are supported: -* `ipaddress` - (Required) The IP address for which to create the firewall rules. - Changing this forces a new resource to be created. +* `ipaddress` - (Required) The IP address or ID for which to create the firewall + rules. Changing this forces a new resource to be created. * `managed` - (Optional) USE WITH CAUTION! If enabled all the firewall rules for this IP address will be managed by this resource. This means it will delete diff --git a/website/source/docs/providers/cloudstack/r/instance.html.markdown b/website/source/docs/providers/cloudstack/r/instance.html.markdown index f97c672833..9f3ffe67bf 100644 --- a/website/source/docs/providers/cloudstack/r/instance.html.markdown +++ b/website/source/docs/providers/cloudstack/r/instance.html.markdown @@ -47,8 +47,8 @@ The following arguments are supported: * `project` - (Optional) The name or ID of the project to deploy this instance to. Changing this forces a new resource to be created. -* `zone` - (Required) The name of the zone where this instance will be created. - Changing this forces a new resource to be created. +* `zone` - (Required) The name or ID of the zone where this instance will be + created. Changing this forces a new resource to be created. * `user_data` - (Optional) The user data to provide when launching the instance. diff --git a/website/source/docs/providers/cloudstack/r/ipaddress.html.markdown b/website/source/docs/providers/cloudstack/r/ipaddress.html.markdown index 3f560d22a5..6a1f0517cd 100644 --- a/website/source/docs/providers/cloudstack/r/ipaddress.html.markdown +++ b/website/source/docs/providers/cloudstack/r/ipaddress.html.markdown @@ -22,10 +22,10 @@ resource "cloudstack_ipaddress" "default" { The following arguments are supported: -* `network` - (Optional) The name of the network for which an IP address should +* `network` - (Optional) The name or ID of the network for which an IP address should be acquired and associated. Changing this forces a new resource to be created. -* `vpc` - (Optional) The name of the VPC for which an IP address should +* `vpc` - (Optional) The name or ID of the VPC for which an IP address should be acquired and associated. Changing this forces a new resource to be created. *NOTE: Either `network` or `vpc` should have a value!* diff --git a/website/source/docs/providers/cloudstack/r/network.html.markdown b/website/source/docs/providers/cloudstack/r/network.html.markdown index 9819a8ce3c..5967554349 100644 --- a/website/source/docs/providers/cloudstack/r/network.html.markdown +++ b/website/source/docs/providers/cloudstack/r/network.html.markdown @@ -37,7 +37,7 @@ The following arguments are supported: * `network_offering` - (Required) The name or ID of the network offering to use for this network. -* `vpc` - (Optional) The name of the VPC to create this network for. Changing +* `vpc` - (Optional) The name or ID of the VPC to create this network for. Changing this forces a new resource to be created. * `aclid` - (Optional) The ID of a network ACL that should be attached to the diff --git a/website/source/docs/providers/cloudstack/r/port_forward.html.markdown b/website/source/docs/providers/cloudstack/r/port_forward.html.markdown index 9c870c4a27..ddf42c75dc 100644 --- a/website/source/docs/providers/cloudstack/r/port_forward.html.markdown +++ b/website/source/docs/providers/cloudstack/r/port_forward.html.markdown @@ -48,7 +48,7 @@ The `forward` block supports: * `public_port` - (Required) The public port to forward from. -* `virtual_machine` - (Required) The name of the virtual machine to forward to. +* `virtual_machine` - (Required) The name or ID of the virtual machine to forward to. ## Attributes Reference diff --git a/website/source/docs/providers/cloudstack/r/secondary_ipaddress.html.markdown b/website/source/docs/providers/cloudstack/r/secondary_ipaddress.html.markdown new file mode 100644 index 0000000000..757673e6a3 --- /dev/null +++ b/website/source/docs/providers/cloudstack/r/secondary_ipaddress.html.markdown @@ -0,0 +1,41 @@ +--- +layout: "cloudstack" +page_title: "CloudStack: cloudstack_secondary_ipaddress" +sidebar_current: "docs-cloudstack-resource-secondary-ipaddress" +description: |- + Assigns a secondary IP to a NIC. +--- + +# cloudstack\_secondary\_ipaddress + +Assigns a secondary IP to a NIC. + +## Example Usage + +``` +resource "cloudstack_secondary_ipaddress" "default" { + virtual_machine = "server-1" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `ipaddress` - (Optional) The IP address to attach the to NIC. If not supplied + an IP address will be selected randomly. Changing this forces a new resource + to be created. + +* `nicid` - (Optional) The ID of the NIC to which you want to attach the + secondary IP address. Changing this forces a new resource to be + created (defaults to the ID of the primary NIC) + +* `virtual_machine` - (Required) The name or ID of the virtual machine to which + you want to attach the secondary IP address. Changing this forces a new + resource to be created. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The secondary IP address ID.