Skip to content

Commit

Permalink
Load balancers: Support HTTP idle timeout & Project ID (#905)
Browse files Browse the repository at this point in the history
* Bump godo version

* Support HTTP idle timeout & project_id

* Attributes with API defaults should be 'Computed: true.'

* Set project_id to state on read.

* Fix up tests.

* Document new LB attributes.

* Support the new attributes in the data source as well.

Co-authored-by: Stephen Varela <[email protected]>
  • Loading branch information
andrewsomething and StephenVarela committed Nov 23, 2022
1 parent c3eed41 commit 9eced33
Show file tree
Hide file tree
Showing 17 changed files with 401 additions and 59 deletions.
13 changes: 13 additions & 0 deletions digitalocean/datasource_digitalocean_loadbalancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,17 @@ func dataSourceDigitalOceanLoadbalancer() *schema.Resource {
Computed: true,
Description: "UUID of the VPC in which the load balancer is located",
},
"http_idle_timeout_seconds": {
Type: schema.TypeInt,
Computed: true,
Description: " Specifies the idle timeout for HTTPS connections on the load balancer.",
},

"project_id": {
Type: schema.TypeString,
Computed: true,
Description: "The ID of the project that the load balancer is associated with.",
},
},
}
}
Expand Down Expand Up @@ -289,6 +300,8 @@ func dataSourceDigitalOceanLoadbalancerRead(ctx context.Context, d *schema.Resou
d.Set("enable_backend_keepalive", foundLoadbalancer.EnableBackendKeepalive)
d.Set("disable_lets_encrypt_dns_records", foundLoadbalancer.DisableLetsEncryptDNSRecords)
d.Set("vpc_uuid", foundLoadbalancer.VPCUUID)
d.Set("http_idle_timeout_seconds", foundLoadbalancer.HTTPIdleTimeoutSeconds)
d.Set("project_id", foundLoadbalancer.ProjectID)

if err := d.Set("droplet_ids", flattenDropletIds(foundLoadbalancer.DropletIDs)); err != nil {
return diag.Errorf("[DEBUG] Error setting Load Balancer droplet_ids - error: %#v", err)
Expand Down
4 changes: 4 additions & 0 deletions digitalocean/datasource_digitalocean_loadbalancer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ data "digitalocean_loadbalancer" "foobar" {
"data.digitalocean_loadbalancer.foobar", "enable_backend_keepalive", "false"),
resource.TestCheckResourceAttr(
"data.digitalocean_loadbalancer.foobar", "disable_lets_encrypt_dns_records", "false"),
resource.TestCheckResourceAttrSet(
"data.digitalocean_loadbalancer.foobar", "project_id"),
resource.TestCheckResourceAttrSet(
"data.digitalocean_loadbalancer.foobar", "http_idle_timeout_seconds"),
),
},
},
Expand Down
22 changes: 22 additions & 0 deletions digitalocean/resource_digitalocean_loadbalancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,18 @@ func resourceDigitalOceanLoadBalancerV0() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},

"http_idle_timeout_seconds": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
},

"project_id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
},
}
}
Expand Down Expand Up @@ -413,6 +425,7 @@ func buildLoadBalancerRequest(client *godo.Client, d *schema.ResourceData) (*god
EnableBackendKeepalive: d.Get("enable_backend_keepalive").(bool),
ForwardingRules: forwardingRules,
DisableLetsEncryptDNSRecords: godo.Bool(d.Get("disable_lets_encrypt_dns_records").(bool)),
ProjectID: d.Get("project_id").(string),
}
sizeUnit, ok := d.GetOk("size_unit")
if ok {
Expand All @@ -421,6 +434,12 @@ func buildLoadBalancerRequest(client *godo.Client, d *schema.ResourceData) (*god
opts.SizeSlug = d.Get("size").(string)
}

idleTimeout, ok := d.GetOk("http_idle_timeout_seconds")
if ok {
t := uint64(idleTimeout.(int))
opts.HTTPIdleTimeoutSeconds = &t
}

if v, ok := d.GetOk("droplet_tag"); ok {
opts.Tag = v.(string)
} else if v, ok := d.GetOk("droplet_ids"); ok {
Expand Down Expand Up @@ -505,6 +524,9 @@ func resourceDigitalOceanLoadbalancerRead(ctx context.Context, d *schema.Resourc
d.Set("enable_backend_keepalive", loadbalancer.EnableBackendKeepalive)
d.Set("droplet_tag", loadbalancer.Tag)
d.Set("vpc_uuid", loadbalancer.VPCUUID)
d.Set("http_idle_timeout_seconds", loadbalancer.HTTPIdleTimeoutSeconds)
d.Set("project_id", loadbalancer.ProjectID)

if loadbalancer.SizeUnit > 0 {
d.Set("size_unit", loadbalancer.SizeUnit)
} else {
Expand Down
124 changes: 117 additions & 7 deletions digitalocean/resource_digitalocean_loadbalancer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"regexp"
"strings"
"testing"
"time"

"github.com/digitalocean/godo"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
Expand Down Expand Up @@ -100,6 +101,10 @@ func TestAccDigitalOceanLoadbalancer_Basic(t *testing.T) {
"digitalocean_loadbalancer.foobar", "enable_backend_keepalive", "true"),
resource.TestCheckResourceAttr(
"digitalocean_loadbalancer.foobar", "disable_lets_encrypt_dns_records", "false"),
resource.TestCheckResourceAttr(
"digitalocean_loadbalancer.foobar", "http_idle_timeout_seconds", "90"),
resource.TestCheckResourceAttrSet(
"digitalocean_loadbalancer.foobar", "project_id"),
),
},
},
Expand Down Expand Up @@ -152,6 +157,10 @@ func TestAccDigitalOceanLoadbalancer_Updated(t *testing.T) {
"digitalocean_loadbalancer.foobar", "enable_backend_keepalive", "true"),
resource.TestCheckResourceAttr(
"digitalocean_loadbalancer.foobar", "disable_lets_encrypt_dns_records", "false"),
resource.TestCheckResourceAttr(
"digitalocean_loadbalancer.foobar", "http_idle_timeout_seconds", "90"),
resource.TestCheckResourceAttrSet(
"digitalocean_loadbalancer.foobar", "project_id"),
),
},
{
Expand Down Expand Up @@ -191,6 +200,10 @@ func TestAccDigitalOceanLoadbalancer_Updated(t *testing.T) {
"digitalocean_loadbalancer.foobar", "enable_backend_keepalive", "false"),
resource.TestCheckResourceAttr(
"digitalocean_loadbalancer.foobar", "disable_lets_encrypt_dns_records", "true"),
resource.TestCheckResourceAttr(
"digitalocean_loadbalancer.foobar", "http_idle_timeout_seconds", "120"),
resource.TestCheckResourceAttrSet(
"digitalocean_loadbalancer.foobar", "project_id"),
),
},
},
Expand Down Expand Up @@ -291,6 +304,75 @@ func TestAccDigitalOceanLoadbalancer_minimal(t *testing.T) {
"digitalocean_loadbalancer.foobar", "enable_proxy_protocol", "false"),
resource.TestCheckResourceAttr(
"digitalocean_loadbalancer.foobar", "enable_backend_keepalive", "false"),
resource.TestCheckResourceAttrSet(
"digitalocean_loadbalancer.foobar", "project_id"),
),
},
},
})
}

func TestAccDigitalOceanLoadbalancer_NonDefaultProject(t *testing.T) {
var loadbalancer godo.LoadBalancer
lbName := randomTestName()
projectName := randomTestName()

projectConfig := `
resource "digitalocean_project" "test" {
name = "%s"
}
`

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: testAccProviderFactories,
CheckDestroy: testAccCheckDigitalOceanLoadbalancerDestroy,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(projectConfig, projectName) + testAccCheckDigitalOceanLoadbalancerConfig_NonDefaultProject(projectName, lbName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckDigitalOceanLoadbalancerExists("digitalocean_loadbalancer.test", &loadbalancer),
resource.TestCheckResourceAttr(
"digitalocean_loadbalancer.test", "name", lbName),
resource.TestCheckResourceAttrPair(
"digitalocean_loadbalancer.test", "project_id", "digitalocean_project.test", "id"),
resource.TestCheckResourceAttr(
"digitalocean_loadbalancer.test", "region", "nyc3"),
resource.TestCheckResourceAttr(
"digitalocean_loadbalancer.test", "size_unit", "1"),
resource.TestCheckResourceAttr(
"digitalocean_loadbalancer.test", "forwarding_rule.#", "1"),
resource.TestCheckTypeSetElemNestedAttrs(
"digitalocean_loadbalancer.test",
"forwarding_rule.*",
map[string]string{
"entry_port": "80",
"entry_protocol": "http",
"target_port": "80",
"target_protocol": "http",
"tls_passthrough": "false",
},
),
resource.TestCheckResourceAttr(
"digitalocean_loadbalancer.test", "healthcheck.#", "1"),
resource.TestCheckResourceAttr(
"digitalocean_loadbalancer.test", "healthcheck.0.port", "80"),
resource.TestCheckResourceAttr(
"digitalocean_loadbalancer.test", "healthcheck.0.protocol", "http"),
),
},
{
// The load balancer must be destroyed before the project which
// discovers that asynchronously.
Config: fmt.Sprintf(projectConfig, projectName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckFunc(
func(s *terraform.State) error {
time.Sleep(10 * time.Second)
return nil
},
),
),
},
},
Expand Down Expand Up @@ -409,6 +491,7 @@ func TestAccDigitalOceanLoadbalancer_sslTermination(t *testing.T) {
var loadbalancer godo.LoadBalancer
rInt := acctest.RandInt()
privateKeyMaterial, leafCertMaterial, certChainMaterial := generateTestCertMaterial(t)
certName := randomTestName()

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Expand All @@ -417,7 +500,7 @@ func TestAccDigitalOceanLoadbalancer_sslTermination(t *testing.T) {
Steps: []resource.TestStep{
{
Config: testAccCheckDigitalOceanLoadbalancerConfig_sslTermination(
"tf-acc-test-certificate-01", rInt, privateKeyMaterial, leafCertMaterial, certChainMaterial, "certificate_id"),
certName, rInt, privateKeyMaterial, leafCertMaterial, certChainMaterial, "certificate_id"),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckDigitalOceanLoadbalancerExists("digitalocean_loadbalancer.foobar", &loadbalancer),
resource.TestCheckResourceAttr(
Expand All @@ -436,7 +519,7 @@ func TestAccDigitalOceanLoadbalancer_sslTermination(t *testing.T) {
"entry_protocol": "https",
"target_port": "80",
"target_protocol": "http",
"certificate_name": "tf-acc-test-certificate-01",
"certificate_name": certName,
"tls_passthrough": "false",
},
),
Expand All @@ -454,6 +537,7 @@ func TestAccDigitalOceanLoadbalancer_sslCertByName(t *testing.T) {
var loadbalancer godo.LoadBalancer
rInt := acctest.RandInt()
privateKeyMaterial, leafCertMaterial, certChainMaterial := generateTestCertMaterial(t)
certName := randomTestName()

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Expand All @@ -462,7 +546,7 @@ func TestAccDigitalOceanLoadbalancer_sslCertByName(t *testing.T) {
Steps: []resource.TestStep{
{
Config: testAccCheckDigitalOceanLoadbalancerConfig_sslTermination(
"tf-acc-test-certificate-02", rInt, privateKeyMaterial, leafCertMaterial, certChainMaterial, "certificate_name"),
certName, rInt, privateKeyMaterial, leafCertMaterial, certChainMaterial, "certificate_name"),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckDigitalOceanLoadbalancerExists("digitalocean_loadbalancer.foobar", &loadbalancer),
resource.TestCheckResourceAttr(
Expand All @@ -479,7 +563,7 @@ func TestAccDigitalOceanLoadbalancer_sslCertByName(t *testing.T) {
"entry_protocol": "https",
"target_port": "80",
"target_protocol": "http",
"certificate_name": "tf-acc-test-certificate-02",
"certificate_name": certName,
"tls_passthrough": "false",
},
),
Expand Down Expand Up @@ -728,6 +812,7 @@ resource "digitalocean_loadbalancer" "foobar" {
enable_proxy_protocol = true
enable_backend_keepalive = true
http_idle_timeout_seconds = 90
droplet_ids = [digitalocean_droplet.foobar.id]
}`, rInt, rInt)
Expand Down Expand Up @@ -769,6 +854,7 @@ resource "digitalocean_loadbalancer" "foobar" {
enable_proxy_protocol = false
enable_backend_keepalive = false
disable_lets_encrypt_dns_records = true
http_idle_timeout_seconds = 120
droplet_ids = [digitalocean_droplet.foobar.id, digitalocean_droplet.foo.id]
}`, rInt, rInt, rInt)
Expand Down Expand Up @@ -821,9 +907,9 @@ resource "digitalocean_droplet" "foobar" {
}
resource "digitalocean_loadbalancer" "foobar" {
name = "loadbalancer-%d"
region = "nyc3"
size = "lb-small"
name = "loadbalancer-%d"
region = "nyc3"
size_unit = 1
forwarding_rule {
entry_port = 80
Expand All @@ -837,6 +923,30 @@ resource "digitalocean_loadbalancer" "foobar" {
}`, rInt, rInt)
}

func testAccCheckDigitalOceanLoadbalancerConfig_NonDefaultProject(projectName, lbName string) string {
return fmt.Sprintf(`
resource "digitalocean_tag" "test" {
name = "%s"
}
resource "digitalocean_loadbalancer" "test" {
name = "%s"
region = "nyc3"
size = "lb-small"
project_id = digitalocean_project.test.id
forwarding_rule {
entry_port = 80
entry_protocol = "http"
target_port = 80
target_protocol = "http"
}
droplet_tag = digitalocean_tag.test.name
}`, projectName, lbName)
}

func testAccCheckDigitalOceanLoadbalancerConfig_minimalUDP(rInt int) string {
return fmt.Sprintf(`
resource "digitalocean_droplet" "foobar" {
Expand Down
3 changes: 2 additions & 1 deletion docs/resources/loadbalancer.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,9 @@ Default value is `false`.
Protocol should be used to pass information from connecting client requests to
the backend service. Default value is `false`.
* `enable_backend_keepalive` - (Optional) A boolean value indicating whether HTTP keepalive connections are maintained to target Droplets. Default value is `false`.
* `http_idle_timeout_seconds` - (Optional) Specifies the idle timeout for HTTPS connections on the load balancer in seconds.
* `disable_lets_encrypt_dns_records` - (Optional) A boolean value indicating whether to disable automatic DNS record creation for Let's Encrypt certificates that are added to the load balancer. Default value is `false`.
* `project_id` - (Optional) The ID of the project that the load balancer is associated with. If no ID is provided at creation, the load balancer associates with the user's default project.
* `vpc_uuid` - (Optional) The ID of the VPC where the load balancer will be located.
* `droplet_ids` (Optional) - A list of the IDs of each droplet to be attached to the Load Balancer.
* `droplet_tag` (Optional) - The name of a Droplet tag corresponding to Droplets to be assigned to the Load Balancer.
Expand All @@ -130,7 +132,6 @@ the backend service. Default value is `false`.
* `cookie_name` - (Optional) The name to be used for the cookie sent to the client. This attribute is required when using `cookies` for the sticky sessions type.
* `cookie_ttl_seconds` - (Optional) The number of seconds until the cookie set by the Load Balancer expires. This attribute is required when using `cookies` for the sticky sessions type.


`healthcheck` supports the following:

* `protocol` - (Required) The protocol used for health checks sent to the backend Droplets. The possible values are `http`, `https` or `tcp`.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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.87.0
github.com/digitalocean/godo v1.90.0
github.com/hashicorp/awspolicyequivalence v1.5.0
github.com/hashicorp/go-uuid v1.0.2
github.com/hashicorp/go-version v1.3.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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.87.0 h1:U6jyE7Ga+6NkAa8pnpgrKk0lEU1e3Fc/kWipC9tARds=
github.com/digitalocean/godo v1.87.0/go.mod h1:NRpFznZFvhHjBoqZAaOD3khVzsJ3EibzKqFL4R60dmA=
github.com/digitalocean/godo v1.90.0 h1:mnluEWL5eXFNYnLzHFuwsPuXZsWmzGoMNYSLZi9QPgc=
github.com/digitalocean/godo v1.90.0/go.mod h1:NRpFznZFvhHjBoqZAaOD3khVzsJ3EibzKqFL4R60dmA=
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=
Expand Down
22 changes: 22 additions & 0 deletions vendor/github.com/digitalocean/godo/CHANGELOG.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 9eced33

Please sign in to comment.