Add reserved IP related resources and data source (#830)
* Replace upstream godo with fork temporarily * Add reserved_ip data source. * Add reserved_ip resource. * Add reserved_ip_assignment resource. * Reduce duplication in data sources. * Add reserved IP docs. * Protect against panic when resp is nil. * Clean up names of test resources. * Add deprecation messaging to docs. * Vendor godo v1.81.0 * Fix typos in docs.
This commit is contained in:
parent
4a36d7dfb1
commit
b1709c2e5d
|
@ -8,11 +8,12 @@ import (
|
|||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
|
||||
)
|
||||
|
||||
func dataSourceDigitalOceanFloatingIp() *schema.Resource {
|
||||
func dataSourceDigitalOceanFloatingIP() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
ReadContext: dataSourceDigitalOceanFloatingIpRead,
|
||||
// TODO: Uncomment when dates for deprecation timeline are set.
|
||||
// DeprecationMessage: "This data source is deprecated and will be removed in a future release. Please use digitalocean_reserved_ip instead.",
|
||||
ReadContext: dataSourceDigitalOceanFloatingIPRead,
|
||||
Schema: map[string]*schema.Schema{
|
||||
|
||||
"ip_address": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
|
@ -39,27 +40,12 @@ func dataSourceDigitalOceanFloatingIp() *schema.Resource {
|
|||
}
|
||||
}
|
||||
|
||||
func dataSourceDigitalOceanFloatingIpRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
client := meta.(*CombinedConfig).godoClient()
|
||||
|
||||
ipAddress := d.Get("ip_address").(string)
|
||||
|
||||
floatingIp, resp, err := client.FloatingIPs.Get(context.Background(), ipAddress)
|
||||
func dataSourceDigitalOceanFloatingIPRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
err := dataSourceDigitalOceanReservedIPRead(ctx, d, meta)
|
||||
if err != nil {
|
||||
if resp != nil && resp.StatusCode == 404 {
|
||||
return diag.Errorf("floating ip not found: %s", err)
|
||||
}
|
||||
return diag.Errorf("Error retrieving floating ip: %s", err)
|
||||
}
|
||||
|
||||
d.SetId(floatingIp.IP)
|
||||
d.Set("ip_address", floatingIp.IP)
|
||||
d.Set("urn", floatingIp.URN())
|
||||
d.Set("region", floatingIp.Region.Slug)
|
||||
|
||||
if floatingIp.Droplet != nil {
|
||||
d.Set("droplet_id", floatingIp.Droplet.ID)
|
||||
return err
|
||||
}
|
||||
reservedIPURNtoFloatingIPURN(d)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
package digitalocean
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
|
||||
)
|
||||
|
||||
func dataSourceDigitalOceanReservedIP() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
ReadContext: dataSourceDigitalOceanReservedIPRead,
|
||||
Schema: map[string]*schema.Schema{
|
||||
"ip_address": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "reserved ip address",
|
||||
ValidateFunc: validation.NoZeroValues,
|
||||
},
|
||||
// computed attributes
|
||||
"urn": {
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
Description: "the uniform resource name for the reserved ip",
|
||||
},
|
||||
"region": {
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
Description: "the region that the reserved ip is reserved to",
|
||||
},
|
||||
"droplet_id": {
|
||||
Type: schema.TypeInt,
|
||||
Computed: true,
|
||||
Description: "the droplet id that the reserved ip has been assigned to.",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func dataSourceDigitalOceanReservedIPRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
ipAddress := d.Get("ip_address").(string)
|
||||
d.SetId(ipAddress)
|
||||
|
||||
return resourceDigitalOceanReservedIPRead(ctx, d, meta)
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
package digitalocean
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/digitalocean/godo"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
|
||||
)
|
||||
|
||||
func TestAccDataSourceDigitalOceanReservedIP_Basic(t *testing.T) {
|
||||
var reservedIP godo.ReservedIP
|
||||
|
||||
expectedURNRegEx, _ := regexp.Compile(`do:reservedip:(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$`)
|
||||
|
||||
resource.ParallelTest(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
ProviderFactories: testAccProviderFactories,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccCheckDataSourceDigitalOceanReservedIPConfig_Basic,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckDataSourceDigitalOceanReservedIPExists("data.digitalocean_reserved_ip.foobar", &reservedIP),
|
||||
resource.TestCheckResourceAttrSet(
|
||||
"data.digitalocean_reserved_ip.foobar", "ip_address"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"data.digitalocean_reserved_ip.foobar", "region", "nyc3"),
|
||||
resource.TestMatchResourceAttr("data.digitalocean_reserved_ip.foobar", "urn", expectedURNRegEx),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccDataSourceDigitalOceanReservedIP_FindsFloatingIP(t *testing.T) {
|
||||
var reservedIP godo.ReservedIP
|
||||
|
||||
expectedURNRegEx, _ := regexp.Compile(`do:reservedip:(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$`)
|
||||
|
||||
resource.ParallelTest(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
ProviderFactories: testAccProviderFactories,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccCheckDataSourceDigitalOceanReservedIPConfig_FindsFloatingIP,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckDataSourceDigitalOceanReservedIPExists("data.digitalocean_reserved_ip.foobar", &reservedIP),
|
||||
resource.TestCheckResourceAttrSet(
|
||||
"data.digitalocean_reserved_ip.foobar", "ip_address"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"data.digitalocean_reserved_ip.foobar", "region", "nyc3"),
|
||||
resource.TestMatchResourceAttr("data.digitalocean_reserved_ip.foobar", "urn", expectedURNRegEx),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckDataSourceDigitalOceanReservedIPExists(n string, reservedIP *godo.ReservedIP) 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 reserved IP ID is set")
|
||||
}
|
||||
|
||||
client := testAccProvider.Meta().(*CombinedConfig).godoClient()
|
||||
|
||||
foundReservedIP, _, err := client.ReservedIPs.Get(context.Background(), rs.Primary.ID)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if foundReservedIP.IP != rs.Primary.ID {
|
||||
return fmt.Errorf("reserved IP not found")
|
||||
}
|
||||
|
||||
*reservedIP = *foundReservedIP
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
const testAccCheckDataSourceDigitalOceanReservedIPConfig_FindsFloatingIP = `
|
||||
resource "digitalocean_floating_ip" "foo" {
|
||||
region = "nyc3"
|
||||
}
|
||||
|
||||
data "digitalocean_reserved_ip" "foobar" {
|
||||
ip_address = digitalocean_floating_ip.foo.ip_address
|
||||
}`
|
||||
|
||||
const testAccCheckDataSourceDigitalOceanReservedIPConfig_Basic = `
|
||||
resource "digitalocean_reserved_ip" "foo" {
|
||||
region = "nyc3"
|
||||
}
|
||||
|
||||
data "digitalocean_reserved_ip" "foobar" {
|
||||
ip_address = digitalocean_reserved_ip.foo.ip_address
|
||||
}`
|
|
@ -0,0 +1,51 @@
|
|||
package digitalocean
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
|
||||
)
|
||||
|
||||
func TestAccDigitalOceanReservedIP_importBasicRegion(t *testing.T) {
|
||||
resourceName := "digitalocean_reserved_ip.foobar"
|
||||
|
||||
resource.ParallelTest(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
ProviderFactories: testAccProviderFactories,
|
||||
CheckDestroy: testAccCheckDigitalOceanReservedIPDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccCheckDigitalOceanReservedIPConfig_region,
|
||||
},
|
||||
|
||||
{
|
||||
ResourceName: resourceName,
|
||||
ImportState: true,
|
||||
ImportStateVerify: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccDigitalOceanReservedIP_importBasicDroplet(t *testing.T) {
|
||||
resourceName := "digitalocean_reserved_ip.foobar"
|
||||
rInt := acctest.RandInt()
|
||||
|
||||
resource.ParallelTest(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
ProviderFactories: testAccProviderFactories,
|
||||
CheckDestroy: testAccCheckDigitalOceanReservedIPDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccCheckDigitalOceanReservedIPConfig_droplet(rInt),
|
||||
},
|
||||
|
||||
{
|
||||
ResourceName: resourceName,
|
||||
ImportState: true,
|
||||
ImportStateVerify: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
|
@ -61,7 +61,7 @@ func Provider() *schema.Provider {
|
|||
"digitalocean_droplets": dataSourceDigitalOceanDroplets(),
|
||||
"digitalocean_droplet_snapshot": dataSourceDigitalOceanDropletSnapshot(),
|
||||
"digitalocean_firewall": dataSourceDigitalOceanFirewall(),
|
||||
"digitalocean_floating_ip": dataSourceDigitalOceanFloatingIp(),
|
||||
"digitalocean_floating_ip": dataSourceDigitalOceanFloatingIP(),
|
||||
"digitalocean_image": dataSourceDigitalOceanImage(),
|
||||
"digitalocean_images": dataSourceDigitalOceanImages(),
|
||||
"digitalocean_kubernetes_cluster": dataSourceDigitalOceanKubernetesCluster(),
|
||||
|
@ -73,6 +73,7 @@ func Provider() *schema.Provider {
|
|||
"digitalocean_records": dataSourceDigitalOceanRecords(),
|
||||
"digitalocean_region": dataSourceDigitalOceanRegion(),
|
||||
"digitalocean_regions": dataSourceDigitalOceanRegions(),
|
||||
"digitalocean_reserved_ip": dataSourceDigitalOceanReservedIP(),
|
||||
"digitalocean_sizes": dataSourceDigitalOceanSizes(),
|
||||
"digitalocean_spaces_bucket": dataSourceDigitalOceanSpacesBucket(),
|
||||
"digitalocean_spaces_buckets": dataSourceDigitalOceanSpacesBuckets(),
|
||||
|
@ -103,8 +104,8 @@ func Provider() *schema.Provider {
|
|||
"digitalocean_droplet": resourceDigitalOceanDroplet(),
|
||||
"digitalocean_droplet_snapshot": resourceDigitalOceanDropletSnapshot(),
|
||||
"digitalocean_firewall": resourceDigitalOceanFirewall(),
|
||||
"digitalocean_floating_ip": resourceDigitalOceanFloatingIp(),
|
||||
"digitalocean_floating_ip_assignment": resourceDigitalOceanFloatingIpAssignment(),
|
||||
"digitalocean_floating_ip": resourceDigitalOceanFloatingIP(),
|
||||
"digitalocean_floating_ip_assignment": resourceDigitalOceanFloatingIPAssignment(),
|
||||
"digitalocean_kubernetes_cluster": resourceDigitalOceanKubernetesCluster(),
|
||||
"digitalocean_kubernetes_node_pool": resourceDigitalOceanKubernetesNodePool(),
|
||||
"digitalocean_loadbalancer": resourceDigitalOceanLoadbalancer(),
|
||||
|
@ -112,6 +113,8 @@ func Provider() *schema.Provider {
|
|||
"digitalocean_project": resourceDigitalOceanProject(),
|
||||
"digitalocean_project_resources": resourceDigitalOceanProjectResources(),
|
||||
"digitalocean_record": resourceDigitalOceanRecord(),
|
||||
"digitalocean_reserved_ip": resourceDigitalOceanReservedIP(),
|
||||
"digitalocean_reserved_ip_assignment": resourceDigitalOceanReservedIPAssignment(),
|
||||
"digitalocean_spaces_bucket": resourceDigitalOceanBucket(),
|
||||
"digitalocean_spaces_bucket_object": resourceDigitalOceanSpacesBucketObject(),
|
||||
"digitalocean_spaces_bucket_policy": resourceDigitalOceanSpacesBucketPolicy(),
|
||||
|
|
|
@ -2,26 +2,24 @@ package digitalocean
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/digitalocean/godo"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
|
||||
)
|
||||
|
||||
func resourceDigitalOceanFloatingIp() *schema.Resource {
|
||||
func resourceDigitalOceanFloatingIP() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
CreateContext: resourceDigitalOceanFloatingIpCreate,
|
||||
UpdateContext: resourceDigitalOceanFloatingIpUpdate,
|
||||
ReadContext: resourceDigitalOceanFloatingIpRead,
|
||||
DeleteContext: resourceDigitalOceanFloatingIpDelete,
|
||||
// TODO: Uncomment when dates for deprecation timeline are set.
|
||||
// DeprecationMessage: "This resource is deprecated and will be removed in a future release. Please use digitalocean_reserved_ip instead.",
|
||||
CreateContext: resourceDigitalOceanFloatingIPCreate,
|
||||
UpdateContext: resourceDigitalOceanFloatingIPUpdate,
|
||||
ReadContext: resourceDigitalOceanFloatingIPRead,
|
||||
DeleteContext: resourceDigitalOceanReservedIPDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
State: resourceDigitalOceanFloatingIpImport,
|
||||
StateContext: resourceDigitalOceanFloatingIPImport,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
|
@ -54,189 +52,49 @@ func resourceDigitalOceanFloatingIp() *schema.Resource {
|
|||
}
|
||||
}
|
||||
|
||||
func resourceDigitalOceanFloatingIpCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
client := meta.(*CombinedConfig).godoClient()
|
||||
|
||||
log.Printf("[INFO] Create a FloatingIP In a Region")
|
||||
regionOpts := &godo.FloatingIPCreateRequest{
|
||||
Region: d.Get("region").(string),
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] FloatingIP Create: %#v", regionOpts)
|
||||
floatingIp, _, err := client.FloatingIPs.Create(context.Background(), regionOpts)
|
||||
func resourceDigitalOceanFloatingIPCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
err := resourceDigitalOceanReservedIPCreate(ctx, d, meta)
|
||||
if err != nil {
|
||||
return diag.Errorf("Error creating FloatingIP: %s", err)
|
||||
}
|
||||
|
||||
d.SetId(floatingIp.IP)
|
||||
|
||||
if v, ok := d.GetOk("droplet_id"); ok {
|
||||
|
||||
log.Printf("[INFO] Assigning the Floating IP to the Droplet %d", v.(int))
|
||||
action, _, err := client.FloatingIPActions.Assign(context.Background(), d.Id(), v.(int))
|
||||
if err != nil {
|
||||
return diag.Errorf(
|
||||
"Error Assigning FloatingIP (%s) to the droplet: %s", d.Id(), err)
|
||||
}
|
||||
|
||||
_, unassignedErr := waitForFloatingIPReady(ctx, d, "completed", []string{"new", "in-progress"}, "status", meta, action.ID)
|
||||
if unassignedErr != nil {
|
||||
return diag.Errorf(
|
||||
"Error waiting for FloatingIP (%s) to be Assigned: %s", d.Id(), unassignedErr)
|
||||
}
|
||||
}
|
||||
|
||||
return resourceDigitalOceanFloatingIpRead(ctx, d, meta)
|
||||
}
|
||||
|
||||
func resourceDigitalOceanFloatingIpUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
client := meta.(*CombinedConfig).godoClient()
|
||||
|
||||
if d.HasChange("droplet_id") {
|
||||
if v, ok := d.GetOk("droplet_id"); ok {
|
||||
log.Printf("[INFO] Assigning the Floating IP %s to the Droplet %d", d.Id(), v.(int))
|
||||
action, _, err := client.FloatingIPActions.Assign(context.Background(), d.Id(), v.(int))
|
||||
if err != nil {
|
||||
return diag.Errorf(
|
||||
"Error Assigning FloatingIP (%s) to the droplet: %s", d.Id(), err)
|
||||
}
|
||||
|
||||
_, unassignedErr := waitForFloatingIPReady(ctx, d, "completed", []string{"new", "in-progress"}, "status", meta, action.ID)
|
||||
if unassignedErr != nil {
|
||||
return diag.Errorf(
|
||||
"Error waiting for FloatingIP (%s) to be Assigned: %s", d.Id(), unassignedErr)
|
||||
}
|
||||
} else {
|
||||
log.Printf("[INFO] Unassigning the Floating IP %s", d.Id())
|
||||
action, _, err := client.FloatingIPActions.Unassign(context.Background(), d.Id())
|
||||
if err != nil {
|
||||
return diag.Errorf(
|
||||
"Error unassigning FloatingIP (%s): %s", d.Id(), err)
|
||||
}
|
||||
|
||||
_, unassignedErr := waitForFloatingIPReady(ctx, d, "completed", []string{"new", "in-progress"}, "status", meta, action.ID)
|
||||
if unassignedErr != nil {
|
||||
return diag.Errorf(
|
||||
"Error waiting for FloatingIP (%s) to be Unassigned: %s", d.Id(), unassignedErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resourceDigitalOceanFloatingIpRead(ctx, d, meta)
|
||||
}
|
||||
|
||||
func resourceDigitalOceanFloatingIpRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
client := meta.(*CombinedConfig).godoClient()
|
||||
|
||||
log.Printf("[INFO] Reading the details of the FloatingIP %s", d.Id())
|
||||
floatingIp, resp, err := client.FloatingIPs.Get(context.Background(), d.Id())
|
||||
if resp.StatusCode != 404 {
|
||||
if err != nil {
|
||||
return diag.Errorf("Error retrieving FloatingIP: %s", err)
|
||||
}
|
||||
|
||||
if _, ok := d.GetOk("droplet_id"); ok && floatingIp.Droplet != nil {
|
||||
log.Printf("[INFO] A droplet was detected on the FloatingIP so setting the Region based on the Droplet")
|
||||
log.Printf("[INFO] The region of the Droplet is %s", floatingIp.Droplet.Region.Slug)
|
||||
d.Set("region", floatingIp.Droplet.Region.Slug)
|
||||
d.Set("droplet_id", floatingIp.Droplet.ID)
|
||||
} else {
|
||||
d.Set("region", floatingIp.Region.Slug)
|
||||
}
|
||||
|
||||
d.Set("ip_address", floatingIp.IP)
|
||||
d.Set("urn", floatingIp.URN())
|
||||
} else {
|
||||
d.SetId("")
|
||||
return err
|
||||
}
|
||||
reservedIPURNtoFloatingIPURN(d)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceDigitalOceanFloatingIpImport(rs *schema.ResourceData, v interface{}) ([]*schema.ResourceData, error) {
|
||||
client := v.(*CombinedConfig).godoClient()
|
||||
floatingIp, resp, err := client.FloatingIPs.Get(context.Background(), rs.Id())
|
||||
if resp.StatusCode != 404 {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rs.Set("ip_address", floatingIp.IP)
|
||||
rs.Set("urn", floatingIp.URN())
|
||||
rs.Set("region", floatingIp.Region.Slug)
|
||||
|
||||
if floatingIp.Droplet != nil {
|
||||
rs.Set("droplet_id", floatingIp.Droplet.ID)
|
||||
}
|
||||
}
|
||||
|
||||
return []*schema.ResourceData{rs}, nil
|
||||
}
|
||||
|
||||
func resourceDigitalOceanFloatingIpDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
client := meta.(*CombinedConfig).godoClient()
|
||||
|
||||
if _, ok := d.GetOk("droplet_id"); ok {
|
||||
log.Printf("[INFO] Unassigning the Floating IP from the Droplet")
|
||||
action, resp, err := client.FloatingIPActions.Unassign(context.Background(), d.Id())
|
||||
if resp.StatusCode != 422 {
|
||||
if err != nil {
|
||||
return diag.Errorf(
|
||||
"Error unassigning FloatingIP (%s) from the droplet: %s", d.Id(), err)
|
||||
}
|
||||
|
||||
_, unassignedErr := waitForFloatingIPReady(ctx, d, "completed", []string{"new", "in-progress"}, "status", meta, action.ID)
|
||||
if unassignedErr != nil {
|
||||
return diag.Errorf(
|
||||
"Error waiting for FloatingIP (%s) to be unassigned: %s", d.Id(), unassignedErr)
|
||||
}
|
||||
} else {
|
||||
log.Printf("[DEBUG] Couldn't unassign FloatingIP (%s) from droplet, possibly out of sync: %s", d.Id(), err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[INFO] Deleting FloatingIP: %s", d.Id())
|
||||
_, err := client.FloatingIPs.Delete(context.Background(), d.Id())
|
||||
func resourceDigitalOceanFloatingIPUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
err := resourceDigitalOceanReservedIPUpdate(ctx, d, meta)
|
||||
if err != nil {
|
||||
return diag.Errorf("Error deleting FloatingIP: %s", err)
|
||||
return err
|
||||
}
|
||||
reservedIPURNtoFloatingIPURN(d)
|
||||
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
|
||||
func waitForFloatingIPReady(
|
||||
ctx context.Context, d *schema.ResourceData, target string, pending []string, attribute string, meta interface{}, actionId int) (interface{}, error) {
|
||||
log.Printf(
|
||||
"[INFO] Waiting for FloatingIP (%s) to have %s of %s",
|
||||
d.Id(), attribute, target)
|
||||
|
||||
stateConf := &resource.StateChangeConf{
|
||||
Pending: pending,
|
||||
Target: []string{target},
|
||||
Refresh: newFloatingIPStateRefreshFunc(d, attribute, meta, actionId),
|
||||
Timeout: 60 * time.Minute,
|
||||
Delay: 10 * time.Second,
|
||||
MinTimeout: 3 * time.Second,
|
||||
|
||||
NotFoundChecks: 60,
|
||||
func resourceDigitalOceanFloatingIPRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
err := resourceDigitalOceanReservedIPRead(ctx, d, meta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reservedIPURNtoFloatingIPURN(d)
|
||||
|
||||
return stateConf.WaitForStateContext(ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
func newFloatingIPStateRefreshFunc(
|
||||
d *schema.ResourceData, attribute string, meta interface{}, actionId int) resource.StateRefreshFunc {
|
||||
client := meta.(*CombinedConfig).godoClient()
|
||||
return func() (interface{}, string, error) {
|
||||
|
||||
log.Printf("[INFO] Assigning the Floating IP to the Droplet")
|
||||
action, _, err := client.FloatingIPActions.Get(context.Background(), d.Id(), actionId)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("Error retrieving FloatingIP (%s) ActionId (%d): %s", d.Id(), actionId, err)
|
||||
}
|
||||
|
||||
log.Printf("[INFO] The FloatingIP Action Status is %s", action.Status)
|
||||
return &action, action.Status, nil
|
||||
func resourceDigitalOceanFloatingIPImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
|
||||
_, err := resourceDigitalOceanReservedIPImport(ctx, d, meta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reservedIPURNtoFloatingIPURN(d)
|
||||
|
||||
return []*schema.ResourceData{d}, nil
|
||||
}
|
||||
|
||||
// reservedIPURNtoFloatingIPURN re-formats a reserved IP URN as floating IP URN.
|
||||
// TODO: Remove when the projects' API changes return values.
|
||||
func reservedIPURNtoFloatingIPURN(d *schema.ResourceData) {
|
||||
ip := d.Get("ip_address")
|
||||
d.Set("urn", godo.FloatingIP{IP: ip.(string)}.URN())
|
||||
}
|
||||
|
|
|
@ -1,27 +1,19 @@
|
|||
package digitalocean
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
|
||||
)
|
||||
|
||||
func resourceDigitalOceanFloatingIpAssignment() *schema.Resource {
|
||||
func resourceDigitalOceanFloatingIPAssignment() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
CreateContext: resourceDigitalOceanFloatingIpAssignmentCreate,
|
||||
ReadContext: resourceDigitalOceanFloatingIpAssignmentRead,
|
||||
DeleteContext: resourceDigitalOceanFloatingIpAssignmentDelete,
|
||||
// TODO: Uncomment when dates for deprecation timeline are set.
|
||||
// DeprecationMessage: "This resource is deprecated and will be removed in a future release. Please use digitalocean_reserved_ip_assignment instead.",
|
||||
CreateContext: resourceDigitalOceanReservedIPAssignmentCreate,
|
||||
ReadContext: resourceDigitalOceanReservedIPAssignmentRead,
|
||||
DeleteContext: resourceDigitalOceanReservedIPAssignmentDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: resourceDigitalOceanFloatingIPAssignmentImport,
|
||||
StateContext: resourceDigitalOceanReservedIPAssignmentImport,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
|
@ -41,131 +33,3 @@ func resourceDigitalOceanFloatingIpAssignment() *schema.Resource {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceDigitalOceanFloatingIpAssignmentCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
client := meta.(*CombinedConfig).godoClient()
|
||||
|
||||
ip_address := d.Get("ip_address").(string)
|
||||
droplet_id := d.Get("droplet_id").(int)
|
||||
|
||||
log.Printf("[INFO] Assigning the Floating IP (%s) to the Droplet %d", ip_address, droplet_id)
|
||||
action, _, err := client.FloatingIPActions.Assign(context.Background(), ip_address, droplet_id)
|
||||
if err != nil {
|
||||
return diag.Errorf(
|
||||
"Error Assigning FloatingIP (%s) to the droplet: %s", ip_address, err)
|
||||
}
|
||||
|
||||
_, unassignedErr := waitForFloatingIPAssignmentReady(ctx, d, "completed", []string{"new", "in-progress"}, "status", meta, action.ID)
|
||||
if unassignedErr != nil {
|
||||
return diag.Errorf(
|
||||
"Error waiting for FloatingIP (%s) to be Assigned: %s", ip_address, unassignedErr)
|
||||
}
|
||||
|
||||
d.SetId(resource.PrefixedUniqueId(fmt.Sprintf("%d-%s-", droplet_id, ip_address)))
|
||||
return resourceDigitalOceanFloatingIpAssignmentRead(ctx, d, meta)
|
||||
}
|
||||
|
||||
func resourceDigitalOceanFloatingIpAssignmentRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
client := meta.(*CombinedConfig).godoClient()
|
||||
|
||||
ip_address := d.Get("ip_address").(string)
|
||||
droplet_id := d.Get("droplet_id").(int)
|
||||
|
||||
log.Printf("[INFO] Reading the details of the FloatingIP %s", ip_address)
|
||||
floatingIp, _, err := client.FloatingIPs.Get(context.Background(), ip_address)
|
||||
if err != nil {
|
||||
return diag.Errorf("Error retrieving FloatingIP: %s", err)
|
||||
}
|
||||
|
||||
if floatingIp.Droplet == nil || floatingIp.Droplet.ID != droplet_id {
|
||||
log.Printf("[INFO] A droplet was detected on the FloatingIP.")
|
||||
d.SetId("")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceDigitalOceanFloatingIpAssignmentDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
client := meta.(*CombinedConfig).godoClient()
|
||||
|
||||
ip_address := d.Get("ip_address").(string)
|
||||
droplet_id := d.Get("droplet_id").(int)
|
||||
|
||||
log.Printf("[INFO] Reading the details of the FloatingIP %s", ip_address)
|
||||
floatingIp, _, err := client.FloatingIPs.Get(context.Background(), ip_address)
|
||||
if err != nil {
|
||||
return diag.Errorf("Error retrieving FloatingIP: %s", err)
|
||||
}
|
||||
|
||||
if floatingIp.Droplet.ID == droplet_id {
|
||||
log.Printf("[INFO] Unassigning the Floating IP from the Droplet")
|
||||
action, _, err := client.FloatingIPActions.Unassign(context.Background(), ip_address)
|
||||
if err != nil {
|
||||
return diag.Errorf("Error unassigning FloatingIP (%s) from the droplet: %s", ip_address, err)
|
||||
}
|
||||
|
||||
_, unassignedErr := waitForFloatingIPAssignmentReady(ctx, d, "completed", []string{"new", "in-progress"}, "status", meta, action.ID)
|
||||
if unassignedErr != nil {
|
||||
return diag.Errorf(
|
||||
"Error waiting for FloatingIP (%s) to be unassigned: %s", ip_address, unassignedErr)
|
||||
}
|
||||
} else {
|
||||
log.Printf("[INFO] Floating IP already unassigned, removing from state.")
|
||||
}
|
||||
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
|
||||
func waitForFloatingIPAssignmentReady(
|
||||
ctx context.Context, d *schema.ResourceData, target string, pending []string, attribute string, meta interface{}, actionId int) (interface{}, error) {
|
||||
log.Printf(
|
||||
"[INFO] Waiting for FloatingIP (%s) to have %s of %s",
|
||||
d.Get("ip_address").(string), attribute, target)
|
||||
|
||||
stateConf := &resource.StateChangeConf{
|
||||
Pending: pending,
|
||||
Target: []string{target},
|
||||
Refresh: newFloatingIPAssignmentStateRefreshFunc(d, attribute, meta, actionId),
|
||||
Timeout: 60 * time.Minute,
|
||||
Delay: 10 * time.Second,
|
||||
MinTimeout: 3 * time.Second,
|
||||
|
||||
NotFoundChecks: 60,
|
||||
}
|
||||
|
||||
return stateConf.WaitForStateContext(ctx)
|
||||
}
|
||||
|
||||
func newFloatingIPAssignmentStateRefreshFunc(
|
||||
d *schema.ResourceData, attribute string, meta interface{}, actionId int) resource.StateRefreshFunc {
|
||||
client := meta.(*CombinedConfig).godoClient()
|
||||
return func() (interface{}, string, error) {
|
||||
|
||||
log.Printf("[INFO] Refreshing the Floating IP state")
|
||||
action, _, err := client.FloatingIPActions.Get(context.Background(), d.Get("ip_address").(string), actionId)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("Error retrieving FloatingIP (%s) ActionId (%d): %s", d.Get("ip_address").(string), actionId, err)
|
||||
}
|
||||
|
||||
log.Printf("[INFO] The FloatingIP Action Status is %s", action.Status)
|
||||
return &action, action.Status, nil
|
||||
}
|
||||
}
|
||||
|
||||
func resourceDigitalOceanFloatingIPAssignmentImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
|
||||
if strings.Contains(d.Id(), ",") {
|
||||
s := strings.Split(d.Id(), ",")
|
||||
d.SetId(resource.PrefixedUniqueId(fmt.Sprintf("%s-%s-", s[1], s[0])))
|
||||
d.Set("ip_address", s[0])
|
||||
dropletID, err := strconv.Atoi(s[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.Set("droplet_id", dropletID)
|
||||
} else {
|
||||
return nil, errors.New("must use the Floating IP and the ID of the Droplet joined with a comma (e.g. `ip_address,droplet_id`)")
|
||||
}
|
||||
|
||||
return []*schema.ResourceData{d}, nil
|
||||
}
|
||||
|
|
|
@ -121,7 +121,7 @@ resource "digitalocean_floating_ip" "foobar" {
|
|||
|
||||
resource "digitalocean_droplet" "foobar" {
|
||||
count = 2
|
||||
name = "foobar-${count.index}"
|
||||
name = "tf-acc-test-${count.index}"
|
||||
size = "s-1vcpu-1gb"
|
||||
image = "centos-7-x64"
|
||||
region = "nyc3"
|
||||
|
@ -142,7 +142,7 @@ resource "digitalocean_floating_ip" "foobar" {
|
|||
|
||||
resource "digitalocean_droplet" "foobar" {
|
||||
count = 2
|
||||
name = "foobar-${count.index}"
|
||||
name = "tf-acc-test-${count.index}"
|
||||
size = "s-1vcpu-1gb"
|
||||
image = "centos-7-x64"
|
||||
region = "nyc3"
|
||||
|
@ -163,7 +163,7 @@ resource "digitalocean_floating_ip" "foobar" {
|
|||
|
||||
resource "digitalocean_droplet" "foobar" {
|
||||
count = 2
|
||||
name = "foobar-${count.index}"
|
||||
name = "tf-acc-test-${count.index}"
|
||||
size = "s-1vcpu-1gb"
|
||||
image = "centos-7-x64"
|
||||
region = "nyc3"
|
||||
|
@ -175,7 +175,7 @@ resource "digitalocean_droplet" "foobar" {
|
|||
var testAccCheckDigitalOceanFloatingIPAssignmentConfig_createBeforeDestroy = `
|
||||
resource "digitalocean_droplet" "foobar" {
|
||||
image = "centos-7-x64"
|
||||
name = "foo-bar"
|
||||
name = "tf-acc-test"
|
||||
region = "nyc3"
|
||||
size = "s-1vcpu-1gb"
|
||||
|
||||
|
@ -201,7 +201,7 @@ resource "digitalocean_floating_ip_assignment" "foobar" {
|
|||
var testAccCheckDigitalOceanFloatingIPAssignmentConfig_createBeforeDestroyReassign = `
|
||||
resource "digitalocean_droplet" "foobar" {
|
||||
image = "ubuntu-18-04-x64"
|
||||
name = "foobar"
|
||||
name = "tf-acc-test"
|
||||
region = "nyc3"
|
||||
size = "s-1vcpu-1gb"
|
||||
|
||||
|
|
|
@ -160,7 +160,7 @@ resource "digitalocean_floating_ip" "foobar" {
|
|||
func testAccCheckDigitalOceanFloatingIPConfig_droplet(rInt int) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "digitalocean_droplet" "foobar" {
|
||||
name = "foobar-%d"
|
||||
name = "tf-acc-test-%d"
|
||||
size = "s-1vcpu-1gb"
|
||||
image = "centos-7-x64"
|
||||
region = "nyc3"
|
||||
|
@ -177,7 +177,7 @@ resource "digitalocean_floating_ip" "foobar" {
|
|||
func testAccCheckDigitalOceanFloatingIPConfig_Reassign(rInt int) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "digitalocean_droplet" "baz" {
|
||||
name = "baz-%d"
|
||||
name = "tf-acc-test-%d"
|
||||
size = "s-1vcpu-1gb"
|
||||
image = "centos-7-x64"
|
||||
region = "nyc3"
|
||||
|
@ -194,7 +194,7 @@ resource "digitalocean_floating_ip" "foobar" {
|
|||
func testAccCheckDigitalOceanFloatingIPConfig_Unassign(rInt int) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "digitalocean_droplet" "baz" {
|
||||
name = "baz-%d"
|
||||
name = "tf-acc-test-%d"
|
||||
size = "s-1vcpu-1gb"
|
||||
image = "centos-7-x64"
|
||||
region = "nyc3"
|
||||
|
|
|
@ -0,0 +1,240 @@
|
|||
package digitalocean
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/digitalocean/godo"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
|
||||
)
|
||||
|
||||
func resourceDigitalOceanReservedIP() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
CreateContext: resourceDigitalOceanReservedIPCreate,
|
||||
UpdateContext: resourceDigitalOceanReservedIPUpdate,
|
||||
ReadContext: resourceDigitalOceanReservedIPRead,
|
||||
DeleteContext: resourceDigitalOceanReservedIPDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: resourceDigitalOceanReservedIPImport,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"region": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
StateFunc: func(val interface{}) string {
|
||||
// DO API V2 region slug is always lowercase
|
||||
return strings.ToLower(val.(string))
|
||||
},
|
||||
},
|
||||
"urn": {
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
Description: "the uniform resource name for the reserved ip",
|
||||
},
|
||||
"ip_address": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
ValidateFunc: validation.IsIPv4Address,
|
||||
},
|
||||
"droplet_id": {
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceDigitalOceanReservedIPCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
client := meta.(*CombinedConfig).godoClient()
|
||||
|
||||
log.Printf("[INFO] Creating a reserved IP in a region")
|
||||
regionOpts := &godo.ReservedIPCreateRequest{
|
||||
Region: d.Get("region").(string),
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Reserved IP create: %#v", regionOpts)
|
||||
reservedIP, _, err := client.ReservedIPs.Create(context.Background(), regionOpts)
|
||||
if err != nil {
|
||||
return diag.Errorf("Error creating reserved IP: %s", err)
|
||||
}
|
||||
|
||||
d.SetId(reservedIP.IP)
|
||||
|
||||
if v, ok := d.GetOk("droplet_id"); ok {
|
||||
log.Printf("[INFO] Assigning the reserved IP to the Droplet %d", v.(int))
|
||||
action, _, err := client.ReservedIPActions.Assign(context.Background(), d.Id(), v.(int))
|
||||
if err != nil {
|
||||
return diag.Errorf(
|
||||
"Error Assigning reserved IP (%s) to the Droplet: %s", d.Id(), err)
|
||||
}
|
||||
|
||||
_, unassignedErr := waitForReservedIPReady(ctx, d, "completed", []string{"new", "in-progress"}, "status", meta, action.ID)
|
||||
if unassignedErr != nil {
|
||||
return diag.Errorf(
|
||||
"Error waiting for reserved IP (%s) to be assigned: %s", d.Id(), unassignedErr)
|
||||
}
|
||||
}
|
||||
|
||||
return resourceDigitalOceanReservedIPRead(ctx, d, meta)
|
||||
}
|
||||
|
||||
func resourceDigitalOceanReservedIPUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
client := meta.(*CombinedConfig).godoClient()
|
||||
|
||||
if d.HasChange("droplet_id") {
|
||||
if v, ok := d.GetOk("droplet_id"); ok {
|
||||
log.Printf("[INFO] Assigning the reserved IP %s to the Droplet %d", d.Id(), v.(int))
|
||||
action, _, err := client.ReservedIPActions.Assign(context.Background(), d.Id(), v.(int))
|
||||
if err != nil {
|
||||
return diag.Errorf(
|
||||
"Error assigning reserved IP (%s) to the Droplet: %s", d.Id(), err)
|
||||
}
|
||||
|
||||
_, unassignedErr := waitForReservedIPReady(ctx, d, "completed", []string{"new", "in-progress"}, "status", meta, action.ID)
|
||||
if unassignedErr != nil {
|
||||
return diag.Errorf(
|
||||
"Error waiting for reserved IP (%s) to be Assigned: %s", d.Id(), unassignedErr)
|
||||
}
|
||||
} else {
|
||||
log.Printf("[INFO] Unassigning the reserved IP %s", d.Id())
|
||||
action, _, err := client.ReservedIPActions.Unassign(context.Background(), d.Id())
|
||||
if err != nil {
|
||||
return diag.Errorf(
|
||||
"Error unassigning reserved IP (%s): %s", d.Id(), err)
|
||||
}
|
||||
|
||||
_, unassignedErr := waitForReservedIPReady(ctx, d, "completed", []string{"new", "in-progress"}, "status", meta, action.ID)
|
||||
if unassignedErr != nil {
|
||||
return diag.Errorf(
|
||||
"Error waiting for reserved IP (%s) to be Unassigned: %s", d.Id(), unassignedErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resourceDigitalOceanReservedIPRead(ctx, d, meta)
|
||||
}
|
||||
|
||||
func resourceDigitalOceanReservedIPRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
client := meta.(*CombinedConfig).godoClient()
|
||||
|
||||
log.Printf("[INFO] Reading the details of the reserved IP %s", d.Id())
|
||||
reservedIP, resp, err := client.ReservedIPs.Get(context.Background(), d.Id())
|
||||
if err != nil {
|
||||
if resp != nil && resp.StatusCode == 404 {
|
||||
log.Printf("[WARN] Reserved IP (%s) not found", d.Id())
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
|
||||
return diag.Errorf("Error retrieving reserved IP: %s", err)
|
||||
}
|
||||
|
||||
if _, ok := d.GetOk("droplet_id"); ok && reservedIP.Droplet != nil {
|
||||
d.Set("region", reservedIP.Droplet.Region.Slug)
|
||||
d.Set("droplet_id", reservedIP.Droplet.ID)
|
||||
} else {
|
||||
d.Set("region", reservedIP.Region.Slug)
|
||||
}
|
||||
|
||||
d.Set("ip_address", reservedIP.IP)
|
||||
d.Set("urn", reservedIP.URN())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceDigitalOceanReservedIPDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
client := meta.(*CombinedConfig).godoClient()
|
||||
|
||||
if _, ok := d.GetOk("droplet_id"); ok {
|
||||
log.Printf("[INFO] Unassigning the reserved IP from the Droplet")
|
||||
action, resp, err := client.ReservedIPActions.Unassign(context.Background(), d.Id())
|
||||
if resp.StatusCode != 422 {
|
||||
if err != nil {
|
||||
return diag.Errorf(
|
||||
"Error unassigning reserved IP (%s) from the droplet: %s", d.Id(), err)
|
||||
}
|
||||
|
||||
_, unassignedErr := waitForReservedIPReady(ctx, d, "completed", []string{"new", "in-progress"}, "status", meta, action.ID)
|
||||
if unassignedErr != nil {
|
||||
return diag.Errorf(
|
||||
"Error waiting for reserved IP (%s) to be unassigned: %s", d.Id(), unassignedErr)
|
||||
}
|
||||
} else {
|
||||
log.Printf("[DEBUG] Couldn't unassign reserved IP (%s) from droplet, possibly out of sync: %s", d.Id(), err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[INFO] Deleting reserved IP: %s", d.Id())
|
||||
_, err := client.ReservedIPs.Delete(context.Background(), d.Id())
|
||||
if err != nil {
|
||||
return diag.Errorf("Error deleting reserved IP: %s", err)
|
||||
}
|
||||
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceDigitalOceanReservedIPImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
|
||||
client := meta.(*CombinedConfig).godoClient()
|
||||
reservedIP, resp, err := client.ReservedIPs.Get(context.Background(), d.Id())
|
||||
if resp.StatusCode != 404 {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d.Set("ip_address", reservedIP.IP)
|
||||
d.Set("urn", reservedIP.URN())
|
||||
d.Set("region", reservedIP.Region.Slug)
|
||||
|
||||
if reservedIP.Droplet != nil {
|
||||
d.Set("droplet_id", reservedIP.Droplet.ID)
|
||||
}
|
||||
}
|
||||
|
||||
return []*schema.ResourceData{d}, nil
|
||||
}
|
||||
|
||||
func waitForReservedIPReady(
|
||||
ctx context.Context, d *schema.ResourceData, target string, pending []string, attribute string, meta interface{}, actionID int) (interface{}, error) {
|
||||
log.Printf(
|
||||
"[INFO] Waiting for reserved IP (%s) to have %s of %s",
|
||||
d.Id(), attribute, target)
|
||||
|
||||
stateConf := &resource.StateChangeConf{
|
||||
Pending: pending,
|
||||
Target: []string{target},
|
||||
Refresh: newReservedIPStateRefreshFunc(d, attribute, meta, actionID),
|
||||
Timeout: 60 * time.Minute,
|
||||
Delay: 10 * time.Second,
|
||||
MinTimeout: 3 * time.Second,
|
||||
|
||||
NotFoundChecks: 60,
|
||||
}
|
||||
|
||||
return stateConf.WaitForStateContext(ctx)
|
||||
}
|
||||
|
||||
func newReservedIPStateRefreshFunc(
|
||||
d *schema.ResourceData, attribute string, meta interface{}, actionID int) resource.StateRefreshFunc {
|
||||
client := meta.(*CombinedConfig).godoClient()
|
||||
return func() (interface{}, string, error) {
|
||||
|
||||
log.Printf("[INFO] Assigning the reserved IP to the Droplet")
|
||||
action, _, err := client.ReservedIPActions.Get(context.Background(), d.Id(), actionID)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("Error retrieving reserved IP (%s) ActionId (%d): %s", d.Id(), actionID, err)
|
||||
}
|
||||
|
||||
log.Printf("[INFO] The reserved IP Action Status is %s", action.Status)
|
||||
return &action, action.Status, nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
package digitalocean
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
|
||||
)
|
||||
|
||||
func resourceDigitalOceanReservedIPAssignment() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
CreateContext: resourceDigitalOceanReservedIPAssignmentCreate,
|
||||
ReadContext: resourceDigitalOceanReservedIPAssignmentRead,
|
||||
DeleteContext: resourceDigitalOceanReservedIPAssignmentDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: resourceDigitalOceanReservedIPAssignmentImport,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"ip_address": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
ValidateFunc: validation.IsIPv4Address,
|
||||
},
|
||||
"droplet_id": {
|
||||
Type: schema.TypeInt,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
ValidateFunc: validation.NoZeroValues,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceDigitalOceanReservedIPAssignmentCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
client := meta.(*CombinedConfig).godoClient()
|
||||
|
||||
ipAddress := d.Get("ip_address").(string)
|
||||
dropletID := d.Get("droplet_id").(int)
|
||||
|
||||
log.Printf("[INFO] Assigning the reserved IP (%s) to the Droplet %d", ipAddress, dropletID)
|
||||
action, _, err := client.ReservedIPActions.Assign(context.Background(), ipAddress, dropletID)
|
||||
if err != nil {
|
||||
return diag.Errorf(
|
||||
"Error Assigning reserved IP (%s) to the droplet: %s", ipAddress, err)
|
||||
}
|
||||
|
||||
_, unassignedErr := waitForReservedIPAssignmentReady(ctx, d, "completed", []string{"new", "in-progress"}, "status", meta, action.ID)
|
||||
if unassignedErr != nil {
|
||||
return diag.Errorf(
|
||||
"Error waiting for reserved IP (%s) to be Assigned: %s", ipAddress, unassignedErr)
|
||||
}
|
||||
|
||||
d.SetId(resource.PrefixedUniqueId(fmt.Sprintf("%d-%s-", dropletID, ipAddress)))
|
||||
return resourceDigitalOceanReservedIPAssignmentRead(ctx, d, meta)
|
||||
}
|
||||
|
||||
func resourceDigitalOceanReservedIPAssignmentRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
client := meta.(*CombinedConfig).godoClient()
|
||||
|
||||
ipAddress := d.Get("ip_address").(string)
|
||||
dropletID := d.Get("droplet_id").(int)
|
||||
|
||||
log.Printf("[INFO] Reading the details of the reserved IP %s", ipAddress)
|
||||
reservedIP, _, err := client.ReservedIPs.Get(context.Background(), ipAddress)
|
||||
if err != nil {
|
||||
return diag.Errorf("Error retrieving reserved IP: %s", err)
|
||||
}
|
||||
|
||||
if reservedIP.Droplet == nil || reservedIP.Droplet.ID != dropletID {
|
||||
log.Printf("[INFO] A Droplet was detected on the reserved IP.")
|
||||
d.SetId("")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceDigitalOceanReservedIPAssignmentDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
client := meta.(*CombinedConfig).godoClient()
|
||||
|
||||
ipAddress := d.Get("ip_address").(string)
|
||||
dropletID := d.Get("droplet_id").(int)
|
||||
|
||||
log.Printf("[INFO] Reading the details of the reserved IP %s", ipAddress)
|
||||
reservedIP, _, err := client.ReservedIPs.Get(context.Background(), ipAddress)
|
||||
if err != nil {
|
||||
return diag.Errorf("Error retrieving reserved IP: %s", err)
|
||||
}
|
||||
|
||||
if reservedIP.Droplet.ID == dropletID {
|
||||
log.Printf("[INFO] Unassigning the reserved IP from the Droplet")
|
||||
action, _, err := client.ReservedIPActions.Unassign(context.Background(), ipAddress)
|
||||
if err != nil {
|
||||
return diag.Errorf("Error unassigning reserved IP (%s) from the droplet: %s", ipAddress, err)
|
||||
}
|
||||
|
||||
_, unassignedErr := waitForReservedIPAssignmentReady(ctx, d, "completed", []string{"new", "in-progress"}, "status", meta, action.ID)
|
||||
if unassignedErr != nil {
|
||||
return diag.Errorf(
|
||||
"Error waiting for reserved IP (%s) to be unassigned: %s", ipAddress, unassignedErr)
|
||||
}
|
||||
} else {
|
||||
log.Printf("[INFO] reserved IP already unassigned, removing from state.")
|
||||
}
|
||||
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
|
||||
func waitForReservedIPAssignmentReady(
|
||||
ctx context.Context, d *schema.ResourceData, target string, pending []string, attribute string, meta interface{}, actionID int) (interface{}, error) {
|
||||
log.Printf(
|
||||
"[INFO] Waiting for reserved IP (%s) to have %s of %s",
|
||||
d.Get("ip_address").(string), attribute, target)
|
||||
|
||||
stateConf := &resource.StateChangeConf{
|
||||
Pending: pending,
|
||||
Target: []string{target},
|
||||
Refresh: newReservedIPAssignmentStateRefreshFunc(d, attribute, meta, actionID),
|
||||
Timeout: 60 * time.Minute,
|
||||
Delay: 10 * time.Second,
|
||||
MinTimeout: 3 * time.Second,
|
||||
|
||||
NotFoundChecks: 60,
|
||||
}
|
||||
|
||||
return stateConf.WaitForStateContext(ctx)
|
||||
}
|
||||
|
||||
func newReservedIPAssignmentStateRefreshFunc(
|
||||
d *schema.ResourceData, attribute string, meta interface{}, actionID int) resource.StateRefreshFunc {
|
||||
client := meta.(*CombinedConfig).godoClient()
|
||||
return func() (interface{}, string, error) {
|
||||
|
||||
log.Printf("[INFO] Refreshing the reserved IP state")
|
||||
action, _, err := client.ReservedIPActions.Get(context.Background(), d.Get("ip_address").(string), actionID)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("Error retrieving reserved IP (%s) ActionId (%d): %s", d.Get("ip_address").(string), actionID, err)
|
||||
}
|
||||
|
||||
log.Printf("[INFO] The reserved IP Action Status is %s", action.Status)
|
||||
return &action, action.Status, nil
|
||||
}
|
||||
}
|
||||
|
||||
func resourceDigitalOceanReservedIPAssignmentImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
|
||||
if strings.Contains(d.Id(), ",") {
|
||||
s := strings.Split(d.Id(), ",")
|
||||
d.SetId(resource.PrefixedUniqueId(fmt.Sprintf("%s-%s-", s[1], s[0])))
|
||||
d.Set("ip_address", s[0])
|
||||
dropletID, err := strconv.Atoi(s[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.Set("droplet_id", dropletID)
|
||||
} else {
|
||||
return nil, errors.New("must use the reserved IP and the ID of the Droplet joined with a comma (e.g. `ip_address,droplet_id`)")
|
||||
}
|
||||
|
||||
return []*schema.ResourceData{d}, nil
|
||||
}
|
|
@ -0,0 +1,225 @@
|
|||
package digitalocean
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/digitalocean/godo"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
|
||||
)
|
||||
|
||||
func TestAccDigitalOceanReservedIPAssignment(t *testing.T) {
|
||||
var reservedIP godo.ReservedIP
|
||||
|
||||
resource.ParallelTest(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
ProviderFactories: testAccProviderFactories,
|
||||
CheckDestroy: testAccCheckDigitalOceanReservedIPDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccCheckDigitalOceanReservedIPAssignmentConfig,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckDigitalOceanReservedIPAttachmentExists("digitalocean_reserved_ip_assignment.foobar"),
|
||||
resource.TestMatchResourceAttr(
|
||||
"digitalocean_reserved_ip_assignment.foobar", "id", regexp.MustCompile("[0-9.]+")),
|
||||
resource.TestMatchResourceAttr(
|
||||
"digitalocean_reserved_ip_assignment.foobar", "droplet_id", regexp.MustCompile("[0-9]+")),
|
||||
),
|
||||
},
|
||||
{
|
||||
Config: testAccCheckDigitalOceanReservedIPAssignmentReassign,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckDigitalOceanReservedIPAttachmentExists("digitalocean_reserved_ip_assignment.foobar"),
|
||||
resource.TestMatchResourceAttr(
|
||||
"digitalocean_reserved_ip_assignment.foobar", "id", regexp.MustCompile("[0-9.]+")),
|
||||
resource.TestMatchResourceAttr(
|
||||
"digitalocean_reserved_ip_assignment.foobar", "droplet_id", regexp.MustCompile("[0-9]+")),
|
||||
),
|
||||
},
|
||||
{
|
||||
Config: testAccCheckDigitalOceanReservedIPAssignmentDeleteAssignment,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckDigitalOceanReservedIPExists("digitalocean_reserved_ip.foobar", &reservedIP),
|
||||
resource.TestMatchResourceAttr(
|
||||
"digitalocean_reserved_ip.foobar", "ip_address", regexp.MustCompile("[0-9.]+")),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccDigitalOceanReservedIPAssignment_createBeforeDestroy(t *testing.T) {
|
||||
resource.ParallelTest(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
ProviderFactories: testAccProviderFactories,
|
||||
CheckDestroy: testAccCheckDigitalOceanReservedIPDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccCheckDigitalOceanReservedIPAssignmentConfig_createBeforeDestroy,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckDigitalOceanReservedIPAttachmentExists("digitalocean_reserved_ip_assignment.foobar"),
|
||||
resource.TestMatchResourceAttr(
|
||||
"digitalocean_reserved_ip_assignment.foobar", "id", regexp.MustCompile("[0-9.]+")),
|
||||
resource.TestMatchResourceAttr(
|
||||
"digitalocean_reserved_ip_assignment.foobar", "droplet_id", regexp.MustCompile("[0-9]+")),
|
||||
),
|
||||
},
|
||||
{
|
||||
Config: testAccCheckDigitalOceanReservedIPAssignmentConfig_createBeforeDestroyReassign,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckDigitalOceanReservedIPAttachmentExists("digitalocean_reserved_ip_assignment.foobar"),
|
||||
resource.TestMatchResourceAttr(
|
||||
"digitalocean_reserved_ip_assignment.foobar", "id", regexp.MustCompile("[0-9.]+")),
|
||||
resource.TestMatchResourceAttr(
|
||||
"digitalocean_reserved_ip_assignment.foobar", "droplet_id", regexp.MustCompile("[0-9]+")),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckDigitalOceanReservedIPAttachmentExists(n string) 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.Attributes["ip_address"] == "" {
|
||||
return fmt.Errorf("No floating IP is set")
|
||||
}
|
||||
fipID := rs.Primary.Attributes["ip_address"]
|
||||
dropletID, err := strconv.Atoi(rs.Primary.Attributes["droplet_id"])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client := testAccProvider.Meta().(*CombinedConfig).godoClient()
|
||||
|
||||
// Try to find the ReservedIP
|
||||
foundReservedIP, _, err := client.ReservedIPs.Get(context.Background(), fipID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if foundReservedIP.IP != fipID || foundReservedIP.Droplet.ID != dropletID {
|
||||
return fmt.Errorf("wrong floating IP attachment found")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var testAccCheckDigitalOceanReservedIPAssignmentConfig = `
|
||||
resource "digitalocean_reserved_ip" "foobar" {
|
||||
region = "nyc3"
|
||||
}
|
||||
|
||||
resource "digitalocean_droplet" "foobar" {
|
||||
count = 2
|
||||
name = "tf-acc-test-${count.index}"
|
||||
size = "s-1vcpu-1gb"
|
||||
image = "centos-7-x64"
|
||||
region = "nyc3"
|
||||
ipv6 = true
|
||||
private_networking = true
|
||||
}
|
||||
|
||||
resource "digitalocean_reserved_ip_assignment" "foobar" {
|
||||
ip_address = digitalocean_reserved_ip.foobar.ip_address
|
||||
droplet_id = digitalocean_droplet.foobar.0.id
|
||||
}
|
||||
`
|
||||
|
||||
var testAccCheckDigitalOceanReservedIPAssignmentReassign = `
|
||||
resource "digitalocean_reserved_ip" "foobar" {
|
||||
region = "nyc3"
|
||||
}
|
||||
|
||||
resource "digitalocean_droplet" "foobar" {
|
||||
count = 2
|
||||
name = "tf-acc-test-${count.index}"
|
||||
size = "s-1vcpu-1gb"
|
||||
image = "centos-7-x64"
|
||||
region = "nyc3"
|
||||
ipv6 = true
|
||||
private_networking = true
|
||||
}
|
||||
|
||||
resource "digitalocean_reserved_ip_assignment" "foobar" {
|
||||
ip_address = digitalocean_reserved_ip.foobar.ip_address
|
||||
droplet_id = digitalocean_droplet.foobar.1.id
|
||||
}
|
||||
`
|
||||
|
||||
var testAccCheckDigitalOceanReservedIPAssignmentDeleteAssignment = `
|
||||
resource "digitalocean_reserved_ip" "foobar" {
|
||||
region = "nyc3"
|
||||
}
|
||||
|
||||
resource "digitalocean_droplet" "foobar" {
|
||||
count = 2
|
||||
name = "tf-acc-test-${count.index}"
|
||||
size = "s-1vcpu-1gb"
|
||||
image = "centos-7-x64"
|
||||
region = "nyc3"
|
||||
ipv6 = true
|
||||
private_networking = true
|
||||
}
|
||||
`
|
||||
|
||||
var testAccCheckDigitalOceanReservedIPAssignmentConfig_createBeforeDestroy = `
|
||||
resource "digitalocean_droplet" "foobar" {
|
||||
image = "centos-7-x64"
|
||||
name = "tf-acc-test"
|
||||
region = "nyc3"
|
||||
size = "s-1vcpu-1gb"
|
||||
|
||||
lifecycle {
|
||||
create_before_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "digitalocean_reserved_ip" "foobar" {
|
||||
region = "nyc3"
|
||||
}
|
||||
|
||||
resource "digitalocean_reserved_ip_assignment" "foobar" {
|
||||
ip_address = digitalocean_reserved_ip.foobar.id
|
||||
droplet_id = digitalocean_droplet.foobar.id
|
||||
|
||||
lifecycle {
|
||||
create_before_destroy = true
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var testAccCheckDigitalOceanReservedIPAssignmentConfig_createBeforeDestroyReassign = `
|
||||
resource "digitalocean_droplet" "foobar" {
|
||||
image = "ubuntu-18-04-x64"
|
||||
name = "tf-acc-test"
|
||||
region = "nyc3"
|
||||
size = "s-1vcpu-1gb"
|
||||
|
||||
lifecycle {
|
||||
create_before_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "digitalocean_reserved_ip" "foobar" {
|
||||
region = "nyc3"
|
||||
}
|
||||
|
||||
resource "digitalocean_reserved_ip_assignment" "foobar" {
|
||||
ip_address = digitalocean_reserved_ip.foobar.id
|
||||
droplet_id = digitalocean_droplet.foobar.id
|
||||
|
||||
lifecycle {
|
||||
create_before_destroy = true
|
||||
}
|
||||
}
|
||||
`
|
|
@ -0,0 +1,208 @@
|
|||
package digitalocean
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/digitalocean/godo"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
|
||||
)
|
||||
|
||||
func init() {
|
||||
resource.AddTestSweepers("digitalocean_reserved_ip", &resource.Sweeper{
|
||||
Name: "digitalocean_reserved_ip",
|
||||
F: testSweepReservedIPs,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func testSweepReservedIPs(region string) error {
|
||||
meta, err := sharedConfigForRegion(region)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client := meta.(*CombinedConfig).godoClient()
|
||||
|
||||
ips, _, err := client.ReservedIPs.List(context.Background(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, ip := range ips {
|
||||
if _, err := client.ReservedIPs.Delete(context.Background(), ip.IP); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestAccDigitalOceanReservedIP_Region(t *testing.T) {
|
||||
var reservedIP godo.ReservedIP
|
||||
|
||||
expectedURNRegEx, _ := regexp.Compile(`do:reservedip:(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$`)
|
||||
|
||||
resource.ParallelTest(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
ProviderFactories: testAccProviderFactories,
|
||||
CheckDestroy: testAccCheckDigitalOceanReservedIPDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccCheckDigitalOceanReservedIPConfig_region,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckDigitalOceanReservedIPExists("digitalocean_reserved_ip.foobar", &reservedIP),
|
||||
resource.TestCheckResourceAttr(
|
||||
"digitalocean_reserved_ip.foobar", "region", "nyc3"),
|
||||
resource.TestMatchResourceAttr("digitalocean_reserved_ip.foobar", "urn", expectedURNRegEx),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccDigitalOceanReservedIP_Droplet(t *testing.T) {
|
||||
var reservedIP godo.ReservedIP
|
||||
rInt := acctest.RandInt()
|
||||
|
||||
resource.ParallelTest(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
ProviderFactories: testAccProviderFactories,
|
||||
CheckDestroy: testAccCheckDigitalOceanReservedIPDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccCheckDigitalOceanReservedIPConfig_droplet(rInt),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckDigitalOceanReservedIPExists("digitalocean_reserved_ip.foobar", &reservedIP),
|
||||
resource.TestCheckResourceAttr(
|
||||
"digitalocean_reserved_ip.foobar", "region", "nyc3"),
|
||||
),
|
||||
},
|
||||
{
|
||||
Config: testAccCheckDigitalOceanReservedIPConfig_Reassign(rInt),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckDigitalOceanReservedIPExists("digitalocean_reserved_ip.foobar", &reservedIP),
|
||||
resource.TestCheckResourceAttr(
|
||||
"digitalocean_reserved_ip.foobar", "region", "nyc3"),
|
||||
),
|
||||
},
|
||||
{
|
||||
Config: testAccCheckDigitalOceanReservedIPConfig_Unassign(rInt),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckDigitalOceanReservedIPExists("digitalocean_reserved_ip.foobar", &reservedIP),
|
||||
resource.TestCheckResourceAttr(
|
||||
"digitalocean_reserved_ip.foobar", "region", "nyc3"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckDigitalOceanReservedIPDestroy(s *terraform.State) error {
|
||||
client := testAccProvider.Meta().(*CombinedConfig).godoClient()
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "digitalocean_reserved_ip" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Try to find the key
|
||||
_, _, err := client.ReservedIPs.Get(context.Background(), rs.Primary.ID)
|
||||
|
||||
if err == nil {
|
||||
return fmt.Errorf("Reserved IP still exists")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCheckDigitalOceanReservedIPExists(n string, reservedIP *godo.ReservedIP) 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")
|
||||
}
|
||||
|
||||
client := testAccProvider.Meta().(*CombinedConfig).godoClient()
|
||||
|
||||
// Try to find the ReservedIP
|
||||
foundReservedIP, _, err := client.ReservedIPs.Get(context.Background(), rs.Primary.ID)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if foundReservedIP.IP != rs.Primary.ID {
|
||||
return fmt.Errorf("Record not found")
|
||||
}
|
||||
|
||||
*reservedIP = *foundReservedIP
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var testAccCheckDigitalOceanReservedIPConfig_region = `
|
||||
resource "digitalocean_reserved_ip" "foobar" {
|
||||
region = "nyc3"
|
||||
}`
|
||||
|
||||
func testAccCheckDigitalOceanReservedIPConfig_droplet(rInt int) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "digitalocean_droplet" "foobar" {
|
||||
name = "tf-acc-test-%d"
|
||||
size = "s-1vcpu-1gb"
|
||||
image = "centos-7-x64"
|
||||
region = "nyc3"
|
||||
ipv6 = true
|
||||
private_networking = true
|
||||
}
|
||||
|
||||
resource "digitalocean_reserved_ip" "foobar" {
|
||||
droplet_id = digitalocean_droplet.foobar.id
|
||||
region = digitalocean_droplet.foobar.region
|
||||
}`, rInt)
|
||||
}
|
||||
|
||||
func testAccCheckDigitalOceanReservedIPConfig_Reassign(rInt int) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "digitalocean_droplet" "baz" {
|
||||
name = "tf-acc-test-%d"
|
||||
size = "s-1vcpu-1gb"
|
||||
image = "centos-7-x64"
|
||||
region = "nyc3"
|
||||
ipv6 = true
|
||||
private_networking = true
|
||||
}
|
||||
|
||||
resource "digitalocean_reserved_ip" "foobar" {
|
||||
droplet_id = digitalocean_droplet.baz.id
|
||||
region = digitalocean_droplet.baz.region
|
||||
}`, rInt)
|
||||
}
|
||||
|
||||
func testAccCheckDigitalOceanReservedIPConfig_Unassign(rInt int) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "digitalocean_droplet" "baz" {
|
||||
name = "tf-acc-test-%d"
|
||||
size = "s-1vcpu-1gb"
|
||||
image = "centos-7-x64"
|
||||
region = "nyc3"
|
||||
ipv6 = true
|
||||
private_networking = true
|
||||
}
|
||||
|
||||
resource "digitalocean_reserved_ip" "foobar" {
|
||||
region = "nyc3"
|
||||
}`, rInt)
|
||||
}
|
|
@ -4,6 +4,8 @@ page_title: "DigitalOcean: digitalocean_floating_ip"
|
|||
|
||||
# digitalocean_floating_ip
|
||||
|
||||
~> **Deprecated:** DigitalOcean Floating IPs have been renamed reserved IPs. This data source will be removed in a future release. Please use `digitalocean_reserved_ip` instead.
|
||||
|
||||
Get information on a floating ip. This data source provides the region and Droplet id
|
||||
as configured on your DigitalOcean account. This is useful if the floating IP
|
||||
in question is not managed by Terraform or you need to find the Droplet the IP is
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
page_title: "DigitalOcean: digitalocean_reserved_ip"
|
||||
---
|
||||
|
||||
# digitalocean_reserved_ip
|
||||
|
||||
Get information on a reserved IP. This data source provides the region and Droplet id
|
||||
as configured on your DigitalOcean account. This is useful if the reserved IP
|
||||
in question is not managed by Terraform or you need to find the Droplet the IP is
|
||||
attached to.
|
||||
|
||||
An error is triggered if the provided reserved IP does not exist.
|
||||
|
||||
## Example Usage
|
||||
|
||||
Get the reserved IP:
|
||||
|
||||
```hcl
|
||||
variable "public_ip" {}
|
||||
|
||||
data "digitalocean_reserved_ip" "example" {
|
||||
ip_address = var.public_ip
|
||||
}
|
||||
|
||||
output "fip_output" {
|
||||
value = data.digitalocean_reserved_ip.example.droplet_id
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `ip_address` - (Required) The allocated IP address of the specific reserved IP to retrieve.
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
The following attributes are exported:
|
||||
|
||||
* `region`: The region that the reserved IP is reserved to.
|
||||
* `urn`: The uniform resource name of the reserved IP.
|
||||
* `droplet_id`: The Droplet id that the reserved IP has been assigned to.
|
|
@ -4,6 +4,8 @@ page_title: "DigitalOcean: digitalocean_floating_ip"
|
|||
|
||||
# digitalocean\_floating_ip
|
||||
|
||||
~> **Deprecated:** DigitalOcean Floating IPs have been renamed reserved IPs. This resource will be removed in a future release. Please use `digitalocean_reserved_ip` instead.
|
||||
|
||||
Provides a DigitalOcean Floating IP to represent a publicly-accessible static IP addresses that can be mapped to one of your Droplets.
|
||||
|
||||
~> **NOTE:** Floating IPs can be assigned to a Droplet either directly on the `digitalocean_floating_ip` resource by setting a `droplet_id` or using the `digitalocean_floating_ip_assignment` resource, but the two cannot be used together.
|
||||
|
|
|
@ -4,6 +4,8 @@ page_title: "DigitalOcean: digitalocean_floating_ip_assignment"
|
|||
|
||||
# digitalocean\_floating_ip_assignment
|
||||
|
||||
~> **Deprecated:** DigitalOcean Floating IPs have been renamed reserved IPs. This resource will be removed in a future release. Please use `digitalocean_reserved_ip_assignment` instead.
|
||||
|
||||
Provides a resource for assigning an existing DigitalOcean Floating IP to a Droplet. This
|
||||
makes it easy to provision floating IP addresses that are not tied to the lifecycle of your
|
||||
Droplet.
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
---
|
||||
page_title: "DigitalOcean: digitalocean_reserved_ip"
|
||||
---
|
||||
|
||||
# digitalocean\_reserved_ip
|
||||
|
||||
Provides a DigitalOcean reserved IP to represent a publicly-accessible static IP addresses that can be mapped to one of your Droplets.
|
||||
|
||||
~> **NOTE:** Reserved IPs can be assigned to a Droplet either directly on the `digitalocean_reserved_ip` resource by setting a `droplet_id` or using the `digitalocean_reserved_ip_assignment` resource, but the two cannot be used together.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```hcl
|
||||
resource "digitalocean_droplet" "example" {
|
||||
name = "example"
|
||||
size = "s-1vcpu-1gb"
|
||||
image = "ubuntu-22-04-x64"
|
||||
region = "nyc3"
|
||||
ipv6 = true
|
||||
private_networking = true
|
||||
}
|
||||
|
||||
resource "digitalocean_reserved_ip" "example" {
|
||||
droplet_id = digitalocean_droplet.example.id
|
||||
region = digitalocean_droplet.example.region
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `region` - (Required) The region that the reserved IP is reserved to.
|
||||
* `droplet_id` - (Optional) The ID of Droplet that the reserved IP will be assigned to.
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
The following attributes are exported:
|
||||
|
||||
* `ip_address` - The IP Address of the resource
|
||||
* `urn` - The uniform resource name of the reserved ip
|
||||
|
||||
## Import
|
||||
|
||||
Reserved IPs can be imported using the `ip`, e.g.
|
||||
|
||||
```
|
||||
terraform import digitalocean_reserved_ip.myip 192.168.0.1
|
||||
```
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
page_title: "DigitalOcean: digitalocean_reserved_ip_assignment"
|
||||
---
|
||||
|
||||
# digitalocean\_reserved_ip_assignment
|
||||
|
||||
Provides a resource for assigning an existing DigitalOcean reserved IP to a Droplet. This
|
||||
makes it easy to provision reserved IP addresses that are not tied to the lifecycle of your
|
||||
Droplet.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```hcl
|
||||
resource "digitalocean_reserved_ip" "example" {
|
||||
region = "nyc3"
|
||||
}
|
||||
|
||||
resource "digitalocean_droplet" "example" {
|
||||
name = "baz"
|
||||
size = "s-1vcpu-1gb"
|
||||
image = "ubuntu-22-04-x64"
|
||||
region = "nyc3"
|
||||
ipv6 = true
|
||||
private_networking = true
|
||||
}
|
||||
|
||||
resource "digitalocean_reserved_ip_assignment" "example" {
|
||||
ip_address = digitalocean_reserved_ip.example.ip_address
|
||||
droplet_id = digitalocean_droplet.example.id
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `ip_address` - (Required) The reserved IP to assign to the Droplet.
|
||||
* `droplet_id` - (Optional) The ID of Droplet that the reserved IP will be assigned to.
|
||||
|
||||
## Import
|
||||
|
||||
Reserved IP assignments can be imported using the reserved IP itself and the `id` of
|
||||
the Droplet joined with a comma. For example:
|
||||
|
||||
```
|
||||
terraform import digitalocean_reserved_ip_assignment.foobar 192.0.2.1,123456
|
||||
```
|
2
go.mod
2
go.mod
|
@ -2,7 +2,7 @@ module github.com/digitalocean/terraform-provider-digitalocean
|
|||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go v1.42.18
|
||||
github.com/digitalocean/godo v1.79.0
|
||||
github.com/digitalocean/godo v1.81.0
|
||||
github.com/hashicorp/awspolicyequivalence v1.5.0
|
||||
github.com/hashicorp/go-uuid v1.0.2
|
||||
github.com/hashicorp/go-version v1.3.0
|
||||
|
|
4
go.sum
4
go.sum
|
@ -81,8 +81,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/digitalocean/godo v1.79.0 h1:Uwef1rh/QLnADR1pR6oyZfqA5zLqondiSnWFPwDzbog=
|
||||
github.com/digitalocean/godo v1.79.0/go.mod h1:BPCqvwbjbGqxuUnIKB4EvS/AX7IDnNmt5fwvIkWo+ew=
|
||||
github.com/digitalocean/godo v1.81.0 h1:sjb3fOfPfSlUQUK22E87BcI8Zx2qtnF7VUCCO4UK3C8=
|
||||
github.com/digitalocean/godo v1.81.0/go.mod h1:BPCqvwbjbGqxuUnIKB4EvS/AX7IDnNmt5fwvIkWo+ew=
|
||||
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
|
||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
|
|
|
@ -1,5 +1,17 @@
|
|||
# Change Log
|
||||
|
||||
## [v1.81.0] - 2022-06-15
|
||||
|
||||
- #532 - @senorprogrammer - Add support for Reserved IP addresses
|
||||
- #538 - @bentranter - util: update droplet create example
|
||||
- #537 - @rpmoore - Adding project_id to databases
|
||||
- #536 - @andrewsomething - account: Now may include info on current team.
|
||||
- #535 - @ElanHasson - APPS-5636 Update App Platform for functions and Starter Tier App Proposals.
|
||||
|
||||
## [v1.80.0] - 2022-05-23
|
||||
|
||||
- #533 - @ElanHasson - APPS-5636 - App Platform updates
|
||||
|
||||
## [v1.79.0] - 2022-04-29
|
||||
|
||||
- #530 - @anitgandhi - monitoring: alerts for Load Balancers TLS conns/s utilization
|
||||
|
|
|
@ -22,14 +22,22 @@ var _ AccountService = &AccountServiceOp{}
|
|||
|
||||
// Account represents a DigitalOcean Account
|
||||
type Account struct {
|
||||
DropletLimit int `json:"droplet_limit,omitempty"`
|
||||
FloatingIPLimit int `json:"floating_ip_limit,omitempty"`
|
||||
VolumeLimit int `json:"volume_limit,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
UUID string `json:"uuid,omitempty"`
|
||||
EmailVerified bool `json:"email_verified,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
StatusMessage string `json:"status_message,omitempty"`
|
||||
DropletLimit int `json:"droplet_limit,omitempty"`
|
||||
FloatingIPLimit int `json:"floating_ip_limit,omitempty"`
|
||||
ReservedIPLimit int `json:"reserved_ip_limit,omitempty"`
|
||||
VolumeLimit int `json:"volume_limit,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
UUID string `json:"uuid,omitempty"`
|
||||
EmailVerified bool `json:"email_verified,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
StatusMessage string `json:"status_message,omitempty"`
|
||||
Team *TeamInfo `json:"team,omitempty"`
|
||||
}
|
||||
|
||||
// TeamInfo contains information about the currently team context.
|
||||
type TeamInfo struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
UUID string `json:"uuid,omitempty"`
|
||||
}
|
||||
|
||||
type accountRoot struct {
|
||||
|
|
|
@ -117,7 +117,7 @@ const (
|
|||
AppAlertSpecOperator_LessThan AppAlertSpecOperator = "LESS_THAN"
|
||||
)
|
||||
|
||||
// AppAlertSpecRule - CPU_UTILIZATION: Represents CPU for a given container instance. Only applicable at the component level. - MEM_UTILIZATION: Represents RAM for a given container instance. Only applicable at the component level. - RESTART_COUNT: Represents restart count for a given container instance. Only applicable at the component level. - DEPLOYMENT_FAILED: Represents whether a deployment has failed. Only applicable at the app level. - DEPLOYMENT_LIVE: Represents whether a deployment has succeeded. Only applicable at the app level. - DOMAIN_FAILED: Represents whether a domain configuration has failed. Only applicable at the app level. - DOMAIN_LIVE: Represents whether a domain configuration has succeeded. Only applicable at the app level. - FUNCTIONS_ACTIVATION_COUNT: Represents an activation count for a given functions instance. Only applicable to functions components. - FUNCTIONS_AVERAGE_DURATION_MS: Represents the average duration for function runtimes. Only applicable to functions components. - FUNCTIONS_ERROR_RATE_PER_MINUTE: Represents an error rate per minute for a given functions instance. Only applicable to functions components. - FUNCTIONS_AVERAGE_WAIT_TIME_MS: Represents the average wait time for functions. Only applicable to functions components. - FUNCTIONS_ERROR_COUNT: Represents an error count for a given functions instance. Only applicable to functions components.
|
||||
// AppAlertSpecRule - CPU_UTILIZATION: Represents CPU for a given container instance. Only applicable at the component level. - MEM_UTILIZATION: Represents RAM for a given container instance. Only applicable at the component level. - RESTART_COUNT: Represents restart count for a given container instance. Only applicable at the component level. - DEPLOYMENT_FAILED: Represents whether a deployment has failed. Only applicable at the app level. - DEPLOYMENT_LIVE: Represents whether a deployment has succeeded. Only applicable at the app level. - DOMAIN_FAILED: Represents whether a domain configuration has failed. Only applicable at the app level. - DOMAIN_LIVE: Represents whether a domain configuration has succeeded. Only applicable at the app level. - FUNCTIONS_ACTIVATION_COUNT: Represents an activation count for a given functions instance. Only applicable to functions components. - FUNCTIONS_AVERAGE_DURATION_MS: Represents the average duration for function runtimes. Only applicable to functions components. - FUNCTIONS_ERROR_RATE_PER_MINUTE: Represents an error rate per minute for a given functions instance. Only applicable to functions components. - FUNCTIONS_AVERAGE_WAIT_TIME_MS: Represents the average wait time for functions. Only applicable to functions components. - FUNCTIONS_ERROR_COUNT: Represents an error count for a given functions instance. Only applicable to functions components. - FUNCTIONS_GB_RATE_PER_SECOND: Represents the rate of memory consumption (GB x seconds) for functions. Only applicable to functions components.
|
||||
type AppAlertSpecRule string
|
||||
|
||||
// List of AppAlertSpecRule
|
||||
|
@ -135,6 +135,7 @@ const (
|
|||
AppAlertSpecRule_FunctionsErrorRatePerMinute AppAlertSpecRule = "FUNCTIONS_ERROR_RATE_PER_MINUTE"
|
||||
AppAlertSpecRule_FunctionsAverageWaitTimeMs AppAlertSpecRule = "FUNCTIONS_AVERAGE_WAIT_TIME_MS"
|
||||
AppAlertSpecRule_FunctionsErrorCount AppAlertSpecRule = "FUNCTIONS_ERROR_COUNT"
|
||||
AppAlertSpecRule_FunctionsGBRatePerSecond AppAlertSpecRule = "FUNCTIONS_GB_RATE_PER_SECOND"
|
||||
)
|
||||
|
||||
// AppAlertSpecWindow the model 'AppAlertSpecWindow'
|
||||
|
@ -189,7 +190,7 @@ type AppDomainSpec struct {
|
|||
// Optional. If the domain uses DigitalOcean DNS and you would like App Platform to automatically manage it for you, set this to the name of the domain on your account. For example, If the domain you are adding is `app.domain.com`, the zone could be `domain.com`.
|
||||
Zone string `json:"zone,omitempty"`
|
||||
Certificate string `json:"certificate,omitempty"`
|
||||
// Optional. The minimum version of TLS a client application can use to access resources for the domain. Must be one of the following values wrapped within quotations: `\"1.0\"`, `\"1.1\"`, `\"1.2\"`, or `\"1.3\"`.
|
||||
// Optional. The minimum version of TLS a client application can use to access resources for the domain. Must be one of the following values wrapped within quotations: `\"1.2\"` or `\"1.3\"`.
|
||||
MinimumTLSVersion string `json:"minimum_tls_version,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -323,7 +324,7 @@ type AppLogDestinationSpecPapertrail struct {
|
|||
type AppRouteSpec struct {
|
||||
// An HTTP path prefix. Paths must start with / and must be unique across all components within an app.
|
||||
Path string `json:"path,omitempty"`
|
||||
// An optional flag to preserve the path that is forwarded to the backend service. By default, the HTTP request path will be trimmed from the left when forwarded to the component. For example, a component with `path=/api` will have requests to `/api/list` trimmed to `/list`. If this value is `true`, the path will remain `/api/list`.
|
||||
// An optional flag to preserve the path that is forwarded to the backend service. By default, the HTTP request path will be trimmed from the left when forwarded to the component. For example, a component with `path=/api` will have requests to `/api/list` trimmed to `/list`. If this value is `true`, the path will remain `/api/list`. Note: this is not applicable for Functions Components.
|
||||
PreservePathPrefix bool `json:"preserve_path_prefix,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -387,14 +388,15 @@ type AppServiceSpecHealthCheck struct {
|
|||
type AppSpec struct {
|
||||
// The name of the app. Must be unique across all apps in the same account.
|
||||
Name string `json:"name"`
|
||||
// Workloads which expose publicy-accessible HTTP services.
|
||||
// Workloads which expose publicly-accessible HTTP services.
|
||||
Services []*AppServiceSpec `json:"services,omitempty"`
|
||||
// Content which can be rendered to static web assets.
|
||||
StaticSites []*AppStaticSiteSpec `json:"static_sites,omitempty"`
|
||||
// Workloads which do not expose publicly-accessible HTTP services.
|
||||
Workers []*AppWorkerSpec `json:"workers,omitempty"`
|
||||
// Pre and post deployment workloads which do not expose publicly-accessible HTTP routes.
|
||||
Jobs []*AppJobSpec `json:"jobs,omitempty"`
|
||||
Jobs []*AppJobSpec `json:"jobs,omitempty"`
|
||||
// Workloads which expose publicly-accessible HTTP services via Functions Components.
|
||||
Functions []*AppFunctionsSpec `json:"functions,omitempty"`
|
||||
// Database instances which can provide persistence to workloads within the application.
|
||||
Databases []*AppDatabaseSpec `json:"databases,omitempty"`
|
||||
|
@ -510,7 +512,7 @@ type AppCORSPolicy struct {
|
|||
AllowHeaders []string `json:"allow_headers,omitempty"`
|
||||
// The set of HTTP response headers that browsers are allowed to access. This configures the Access-Control-Expose-Headers header.
|
||||
ExposeHeaders []string `json:"expose_headers,omitempty"`
|
||||
// An optional duration specifiying how long browsers can cache the results of a preflight request. This configures the Access-Control-Max-Age header. Example: `5h30m`.
|
||||
// An optional duration specifying how long browsers can cache the results of a preflight request. This configures the Access-Control-Max-Age header. Example: `5h30m`.
|
||||
MaxAge string `json:"max_age,omitempty"`
|
||||
// Whether browsers should expose the response to the client-side JavaScript code when the request's credentials mode is `include`. This configures the Access-Control-Allow-Credentials header.
|
||||
AllowCredentials bool `json:"allow_credentials,omitempty"`
|
||||
|
@ -803,20 +805,29 @@ type AppProposeRequest struct {
|
|||
|
||||
// AppProposeResponse struct for AppProposeResponse
|
||||
type AppProposeResponse struct {
|
||||
AppIsStatic bool `json:"app_is_static,omitempty"`
|
||||
// Deprecated. Please use AppIsStarter instead.
|
||||
AppIsStatic bool `json:"app_is_static,omitempty"`
|
||||
// Indicates whether the app name is available.
|
||||
AppNameAvailable bool `json:"app_name_available,omitempty"`
|
||||
// If the app name is unavailable, this will be set to a suggested available name.
|
||||
AppNameSuggestion string `json:"app_name_suggestion,omitempty"`
|
||||
// The number of existing static apps the account has.
|
||||
// Deprecated. Please use ExistingStarterApps instead.
|
||||
ExistingStaticApps string `json:"existing_static_apps,omitempty"`
|
||||
// The maximum number of free static apps the account can have. Any additional static apps will be charged for.
|
||||
// Deprecated. Please use MaxFreeStarterApps instead.
|
||||
MaxFreeStaticApps string `json:"max_free_static_apps,omitempty"`
|
||||
Spec *AppSpec `json:"spec,omitempty"`
|
||||
AppCost float32 `json:"app_cost,omitempty"`
|
||||
// The monthly cost of the proposed app in USD using the next pricing plan tier. For example, if you propose an app that uses the Basic tier, the `app_tier_upgrade_cost` field displays the monthly cost of the app if it were to use the Professional tier. If the proposed app already uses the most expensive tier, the field is empty.
|
||||
// The monthly cost of the proposed app in USD.
|
||||
AppCost float32 `json:"app_cost,omitempty"`
|
||||
// The monthly cost of the proposed app in USD using the next pricing plan tier. For example, if you propose an app that uses the Basic tier, the `AppTierUpgradeCost` field displays the monthly cost of the app if it were to use the Professional tier. If the proposed app already uses the most expensive tier, the field is empty.
|
||||
AppTierUpgradeCost float32 `json:"app_tier_upgrade_cost,omitempty"`
|
||||
// The monthly cost of the proposed app in USD using the previous pricing plan tier. For example, if you propose an app that uses the Professional tier, the `app_tier_downgrade_cost` field displays the monthly cost of the app if it were to use the Basic tier. If the proposed app already uses the lest expensive tier, the field is empty.
|
||||
// The monthly cost of the proposed app in USD using the previous pricing plan tier. For example, if you propose an app that uses the Professional tier, the `AppTierDowngradeCost` field displays the monthly cost of the app if it were to use the Basic tier. If the proposed app already uses the lest expensive tier, the field is empty.
|
||||
AppTierDowngradeCost float32 `json:"app_tier_downgrade_cost,omitempty"`
|
||||
// The number of existing starter tier apps the account has.
|
||||
ExistingStarterApps string `json:"existing_starter_apps,omitempty"`
|
||||
// The maximum number of free starter apps the account can have. Any additional starter apps will be charged for. These include apps with only static sites, functions, and databases.
|
||||
MaxFreeStarterApps string `json:"max_free_starter_apps,omitempty"`
|
||||
// Indicates whether the app is a starter tier app.
|
||||
AppIsStarter bool `json:"app_is_starter,omitempty"`
|
||||
}
|
||||
|
||||
// AppRegion struct for AppRegion
|
||||
|
|
|
@ -154,6 +154,7 @@ type Database struct {
|
|||
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
PrivateNetworkUUID string `json:"private_network_uuid,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
ProjectID string `json:"project_id,omitempty"`
|
||||
}
|
||||
|
||||
// DatabaseCA represents a database ca.
|
||||
|
@ -217,6 +218,7 @@ type DatabaseCreateRequest struct {
|
|||
PrivateNetworkUUID string `json:"private_network_uuid"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
BackupRestore *DatabaseBackupRestore `json:"backup_restore,omitempty"`
|
||||
ProjectID string `json:"project_id"`
|
||||
}
|
||||
|
||||
// DatabaseResizeRequest can be used to initiate a database resize operation.
|
||||
|
|
|
@ -20,7 +20,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
libraryVersion = "1.79.0"
|
||||
libraryVersion = "1.81.0"
|
||||
defaultBaseURL = "https://api.digitalocean.com/"
|
||||
userAgent = "godo/" + libraryVersion
|
||||
mediaType = "application/json"
|
||||
|
@ -64,6 +64,8 @@ type Client struct {
|
|||
Sizes SizesService
|
||||
FloatingIPs FloatingIPsService
|
||||
FloatingIPActions FloatingIPActionsService
|
||||
ReservedIPs ReservedIPsService
|
||||
ReservedIPActions ReservedIPActionsService
|
||||
Snapshots SnapshotsService
|
||||
Storage StorageService
|
||||
StorageActions StorageActionsService
|
||||
|
@ -219,6 +221,8 @@ func NewClient(httpClient *http.Client) *Client {
|
|||
c.Firewalls = &FirewallsServiceOp{client: c}
|
||||
c.FloatingIPs = &FloatingIPsServiceOp{client: c}
|
||||
c.FloatingIPActions = &FloatingIPActionsServiceOp{client: c}
|
||||
c.ReservedIPs = &ReservedIPsServiceOp{client: c}
|
||||
c.ReservedIPActions = &ReservedIPActionsServiceOp{client: c}
|
||||
c.Images = &ImagesServiceOp{client: c}
|
||||
c.ImageActions = &ImageActionsServiceOp{client: c}
|
||||
c.Invoices = &InvoicesServiceOp{client: c}
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
package godo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const resourceType = "ReservedIP"
|
||||
const reservedIPsBasePath = "v2/reserved_ips"
|
||||
|
||||
// ReservedIPsService is an interface for interfacing with the reserved IPs
|
||||
// endpoints of the Digital Ocean API.
|
||||
// See: https://docs.digitalocean.com/reference/api/api-reference/#tag/Reserved-IPs
|
||||
type ReservedIPsService interface {
|
||||
List(context.Context, *ListOptions) ([]ReservedIP, *Response, error)
|
||||
Get(context.Context, string) (*ReservedIP, *Response, error)
|
||||
Create(context.Context, *ReservedIPCreateRequest) (*ReservedIP, *Response, error)
|
||||
Delete(context.Context, string) (*Response, error)
|
||||
}
|
||||
|
||||
// ReservedIPsServiceOp handles communication with the reserved IPs related methods of the
|
||||
// DigitalOcean API.
|
||||
type ReservedIPsServiceOp struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
var _ ReservedIPsService = &ReservedIPsServiceOp{}
|
||||
|
||||
// ReservedIP represents a Digital Ocean reserved IP.
|
||||
type ReservedIP struct {
|
||||
Region *Region `json:"region"`
|
||||
Droplet *Droplet `json:"droplet"`
|
||||
IP string `json:"ip"`
|
||||
}
|
||||
|
||||
func (f ReservedIP) String() string {
|
||||
return Stringify(f)
|
||||
}
|
||||
|
||||
// URN returns the reserved IP in a valid DO API URN form.
|
||||
func (f ReservedIP) URN() string {
|
||||
return ToURN(resourceType, f.IP)
|
||||
}
|
||||
|
||||
type reservedIPsRoot struct {
|
||||
ReservedIPs []ReservedIP `json:"reserved_ips"`
|
||||
Links *Links `json:"links"`
|
||||
Meta *Meta `json:"meta"`
|
||||
}
|
||||
|
||||
type reservedIPRoot struct {
|
||||
ReservedIP *ReservedIP `json:"reserved_ip"`
|
||||
Links *Links `json:"links,omitempty"`
|
||||
}
|
||||
|
||||
// ReservedIPCreateRequest represents a request to create a reserved IP.
|
||||
// Specify DropletID to assign the reserved IP to a Droplet or Region
|
||||
// to reserve it to the region.
|
||||
type ReservedIPCreateRequest struct {
|
||||
Region string `json:"region,omitempty"`
|
||||
DropletID int `json:"droplet_id,omitempty"`
|
||||
}
|
||||
|
||||
// List all reserved IPs.
|
||||
func (r *ReservedIPsServiceOp) List(ctx context.Context, opt *ListOptions) ([]ReservedIP, *Response, error) {
|
||||
path := reservedIPsBasePath
|
||||
path, err := addOptions(path, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := r.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(reservedIPsRoot)
|
||||
resp, err := r.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
if l := root.Links; l != nil {
|
||||
resp.Links = l
|
||||
}
|
||||
if m := root.Meta; m != nil {
|
||||
resp.Meta = m
|
||||
}
|
||||
|
||||
return root.ReservedIPs, resp, err
|
||||
}
|
||||
|
||||
// Get an individual reserved IP.
|
||||
func (r *ReservedIPsServiceOp) Get(ctx context.Context, ip string) (*ReservedIP, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", reservedIPsBasePath, ip)
|
||||
|
||||
req, err := r.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(reservedIPRoot)
|
||||
resp, err := r.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return root.ReservedIP, resp, err
|
||||
}
|
||||
|
||||
// Create a reserved IP. If the DropletID field of the request is not empty,
|
||||
// the reserved IP will also be assigned to the droplet.
|
||||
func (r *ReservedIPsServiceOp) Create(ctx context.Context, createRequest *ReservedIPCreateRequest) (*ReservedIP, *Response, error) {
|
||||
path := reservedIPsBasePath
|
||||
|
||||
req, err := r.client.NewRequest(ctx, http.MethodPost, path, createRequest)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(reservedIPRoot)
|
||||
resp, err := r.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
if l := root.Links; l != nil {
|
||||
resp.Links = l
|
||||
}
|
||||
|
||||
return root.ReservedIP, resp, err
|
||||
}
|
||||
|
||||
// Delete a reserved IP.
|
||||
func (r *ReservedIPsServiceOp) Delete(ctx context.Context, ip string) (*Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", reservedIPsBasePath, ip)
|
||||
|
||||
req, err := r.client.NewRequest(ctx, http.MethodDelete, path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := r.client.Do(ctx, req, nil)
|
||||
|
||||
return resp, err
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
package godo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// ReservedIPActionsService is an interface for interfacing with the
|
||||
// reserved IPs actions endpoints of the Digital Ocean API.
|
||||
// See: https://docs.digitalocean.com/reference/api/api-reference/#tag/Reserved-IP-Actions
|
||||
type ReservedIPActionsService interface {
|
||||
Assign(ctx context.Context, ip string, dropletID int) (*Action, *Response, error)
|
||||
Unassign(ctx context.Context, ip string) (*Action, *Response, error)
|
||||
Get(ctx context.Context, ip string, actionID int) (*Action, *Response, error)
|
||||
List(ctx context.Context, ip string, opt *ListOptions) ([]Action, *Response, error)
|
||||
}
|
||||
|
||||
// ReservedIPActionsServiceOp handles communication with the reserved IPs
|
||||
// action related methods of the DigitalOcean API.
|
||||
type ReservedIPActionsServiceOp struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// Assign a reserved IP to a droplet.
|
||||
func (s *ReservedIPActionsServiceOp) Assign(ctx context.Context, ip string, dropletID int) (*Action, *Response, error) {
|
||||
request := &ActionRequest{
|
||||
"type": "assign",
|
||||
"droplet_id": dropletID,
|
||||
}
|
||||
return s.doAction(ctx, ip, request)
|
||||
}
|
||||
|
||||
// Unassign a rerserved IP from the droplet it is currently assigned to.
|
||||
func (s *ReservedIPActionsServiceOp) Unassign(ctx context.Context, ip string) (*Action, *Response, error) {
|
||||
request := &ActionRequest{"type": "unassign"}
|
||||
return s.doAction(ctx, ip, request)
|
||||
}
|
||||
|
||||
// Get an action for a particular reserved IP by id.
|
||||
func (s *ReservedIPActionsServiceOp) Get(ctx context.Context, ip string, actionID int) (*Action, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%d", reservedIPActionPath(ip), actionID)
|
||||
return s.get(ctx, path)
|
||||
}
|
||||
|
||||
// List the actions for a particular reserved IP.
|
||||
func (s *ReservedIPActionsServiceOp) List(ctx context.Context, ip string, opt *ListOptions) ([]Action, *Response, error) {
|
||||
path := reservedIPActionPath(ip)
|
||||
path, err := addOptions(path, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return s.list(ctx, path)
|
||||
}
|
||||
|
||||
func (s *ReservedIPActionsServiceOp) doAction(ctx context.Context, ip string, request *ActionRequest) (*Action, *Response, error) {
|
||||
path := reservedIPActionPath(ip)
|
||||
|
||||
req, err := s.client.NewRequest(ctx, http.MethodPost, path, request)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(actionRoot)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return root.Event, resp, err
|
||||
}
|
||||
|
||||
func (s *ReservedIPActionsServiceOp) get(ctx context.Context, path string) (*Action, *Response, error) {
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(actionRoot)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return root.Event, resp, err
|
||||
}
|
||||
|
||||
func (s *ReservedIPActionsServiceOp) list(ctx context.Context, path string) ([]Action, *Response, error) {
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(actionsRoot)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
if l := root.Links; l != nil {
|
||||
resp.Links = l
|
||||
}
|
||||
|
||||
return root.Actions, resp, err
|
||||
}
|
||||
|
||||
func reservedIPActionPath(ip string) string {
|
||||
return fmt.Sprintf("%s/%s/actions", reservedIPsBasePath, ip)
|
||||
}
|
|
@ -64,7 +64,7 @@ github.com/aws/aws-sdk-go/service/sts/stsiface
|
|||
# github.com/davecgh/go-spew v1.1.1
|
||||
## explicit
|
||||
github.com/davecgh/go-spew/spew
|
||||
# github.com/digitalocean/godo v1.79.0
|
||||
# github.com/digitalocean/godo v1.81.0
|
||||
## explicit; go 1.18
|
||||
github.com/digitalocean/godo
|
||||
github.com/digitalocean/godo/metrics
|
||||
|
|
Loading…
Reference in New Issue