Added basic validation with minor optimizations/fixes to the load balancer resource
This commit is contained in:
parent
fc729120f9
commit
eecd675b76
|
@ -3,10 +3,10 @@ package digitalocean
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/digitalocean/godo"
|
"github.com/digitalocean/godo"
|
||||||
"github.com/hashicorp/terraform/helper/resource"
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
func loadbalancerStateRefreshFunc(client *godo.Client, loadbalancerId string) resource.StateRefreshFunc {
|
func loadbalancerStateRefreshFunc(client *godo.Client, loadbalancerId string) resource.StateRefreshFunc {
|
||||||
|
@ -82,13 +82,12 @@ func expandForwardingRules(config []interface{}) []godo.ForwardingRule {
|
||||||
return forwardingRules
|
return forwardingRules
|
||||||
}
|
}
|
||||||
|
|
||||||
func flattenDropletIds(list []int) []interface{} {
|
func flattenDropletIds(list []int) *schema.Set {
|
||||||
flatList := make([]interface{}, 0, len(list))
|
flatSet := schema.NewSet(schema.HashInt, []interface{}{})
|
||||||
for _, v := range list {
|
for _, v := range list {
|
||||||
vStr := strconv.Itoa(v)
|
flatSet.Add(v)
|
||||||
flatList = append(flatList, vStr)
|
|
||||||
}
|
}
|
||||||
return flatList
|
return flatSet
|
||||||
}
|
}
|
||||||
|
|
||||||
func flattenHealthChecks(health *godo.HealthCheck) []map[string]interface{} {
|
func flattenHealthChecks(health *godo.HealthCheck) []map[string]interface{} {
|
||||||
|
|
|
@ -4,12 +4,12 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/digitalocean/godo"
|
"github.com/digitalocean/godo"
|
||||||
"github.com/hashicorp/terraform/helper/resource"
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/hashicorp/terraform/helper/validation"
|
||||||
)
|
)
|
||||||
|
|
||||||
func resourceDigitalOceanLoadbalancer() *schema.Resource {
|
func resourceDigitalOceanLoadbalancer() *schema.Resource {
|
||||||
|
@ -23,21 +23,26 @@ func resourceDigitalOceanLoadbalancer() *schema.Resource {
|
||||||
},
|
},
|
||||||
|
|
||||||
Schema: map[string]*schema.Schema{
|
Schema: map[string]*schema.Schema{
|
||||||
"name": {
|
|
||||||
Type: schema.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
"region": {
|
"region": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Required: true,
|
Required: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"name": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ValidateFunc: validation.NoZeroValues,
|
||||||
|
},
|
||||||
|
|
||||||
"algorithm": {
|
"algorithm": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Default: "round_robin",
|
Default: "round_robin",
|
||||||
|
ValidateFunc: validation.StringInSlice([]string{
|
||||||
|
"round_robin",
|
||||||
|
"least_connections",
|
||||||
|
}, false),
|
||||||
},
|
},
|
||||||
|
|
||||||
"forwarding_rule": {
|
"forwarding_rule": {
|
||||||
|
@ -49,22 +54,37 @@ func resourceDigitalOceanLoadbalancer() *schema.Resource {
|
||||||
"entry_protocol": {
|
"entry_protocol": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Required: true,
|
Required: true,
|
||||||
|
ValidateFunc: validation.StringInSlice([]string{
|
||||||
|
"http",
|
||||||
|
"https",
|
||||||
|
"http2",
|
||||||
|
"tcp",
|
||||||
|
}, false),
|
||||||
},
|
},
|
||||||
"entry_port": {
|
"entry_port": {
|
||||||
Type: schema.TypeInt,
|
Type: schema.TypeInt,
|
||||||
Required: true,
|
Required: true,
|
||||||
|
ValidateFunc: validation.IntBetween(1, 65535),
|
||||||
},
|
},
|
||||||
"target_protocol": {
|
"target_protocol": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Required: true,
|
Required: true,
|
||||||
|
ValidateFunc: validation.StringInSlice([]string{
|
||||||
|
"http",
|
||||||
|
"https",
|
||||||
|
"http2",
|
||||||
|
"tcp",
|
||||||
|
}, false),
|
||||||
},
|
},
|
||||||
"target_port": {
|
"target_port": {
|
||||||
Type: schema.TypeInt,
|
Type: schema.TypeInt,
|
||||||
Required: true,
|
Required: true,
|
||||||
|
ValidateFunc: validation.IntBetween(1, 65535),
|
||||||
},
|
},
|
||||||
"certificate_id": {
|
"certificate_id": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
ValidateFunc: validation.NoZeroValues,
|
||||||
},
|
},
|
||||||
"tls_passthrough": {
|
"tls_passthrough": {
|
||||||
Type: schema.TypeBool,
|
Type: schema.TypeBool,
|
||||||
|
@ -78,40 +98,51 @@ func resourceDigitalOceanLoadbalancer() *schema.Resource {
|
||||||
"healthcheck": {
|
"healthcheck": {
|
||||||
Type: schema.TypeList,
|
Type: schema.TypeList,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
MaxItems: 1,
|
MaxItems: 1,
|
||||||
Elem: &schema.Resource{
|
Elem: &schema.Resource{
|
||||||
Schema: map[string]*schema.Schema{
|
Schema: map[string]*schema.Schema{
|
||||||
"protocol": {
|
"protocol": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Required: true,
|
Required: true,
|
||||||
|
ValidateFunc: validation.StringInSlice([]string{
|
||||||
|
"http",
|
||||||
|
"tcp",
|
||||||
|
}, false),
|
||||||
},
|
},
|
||||||
"port": {
|
"port": {
|
||||||
Type: schema.TypeInt,
|
Type: schema.TypeInt,
|
||||||
Required: true,
|
Required: true,
|
||||||
|
ValidateFunc: validation.IntBetween(1, 65535),
|
||||||
},
|
},
|
||||||
"path": {
|
"path": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
ValidateFunc: validation.NoZeroValues,
|
||||||
},
|
},
|
||||||
"check_interval_seconds": {
|
"check_interval_seconds": {
|
||||||
Type: schema.TypeInt,
|
Type: schema.TypeInt,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Default: 10,
|
Default: 10,
|
||||||
|
ValidateFunc: validation.IntBetween(3, 300),
|
||||||
},
|
},
|
||||||
"response_timeout_seconds": {
|
"response_timeout_seconds": {
|
||||||
Type: schema.TypeInt,
|
Type: schema.TypeInt,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Default: 5,
|
Default: 5,
|
||||||
|
ValidateFunc: validation.IntBetween(3, 300),
|
||||||
},
|
},
|
||||||
"unhealthy_threshold": {
|
"unhealthy_threshold": {
|
||||||
Type: schema.TypeInt,
|
Type: schema.TypeInt,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Default: 3,
|
Default: 3,
|
||||||
|
ValidateFunc: validation.IntBetween(2, 10),
|
||||||
},
|
},
|
||||||
"healthy_threshold": {
|
"healthy_threshold": {
|
||||||
Type: schema.TypeInt,
|
Type: schema.TypeInt,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Default: 5,
|
Default: 5,
|
||||||
|
ValidateFunc: validation.IntBetween(2, 10),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -128,24 +159,31 @@ func resourceDigitalOceanLoadbalancer() *schema.Resource {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Default: "none",
|
Default: "none",
|
||||||
|
ValidateFunc: validation.StringInSlice([]string{
|
||||||
|
"cookies",
|
||||||
|
"none",
|
||||||
|
}, false),
|
||||||
},
|
},
|
||||||
"cookie_name": {
|
"cookie_name": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
ValidateFunc: validation.StringLenBetween(2, 40),
|
||||||
},
|
},
|
||||||
"cookie_ttl_seconds": {
|
"cookie_ttl_seconds": {
|
||||||
Type: schema.TypeInt,
|
Type: schema.TypeInt,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
ValidateFunc: validation.IntAtLeast(1),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
"droplet_ids": {
|
"droplet_ids": {
|
||||||
Type: schema.TypeList,
|
Type: schema.TypeSet,
|
||||||
Elem: &schema.Schema{Type: schema.TypeString},
|
Elem: &schema.Schema{Type: schema.TypeInt},
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
|
ConflictsWith: []string{"droplet_tag"},
|
||||||
},
|
},
|
||||||
|
|
||||||
"droplet_tag": {
|
"droplet_tag": {
|
||||||
|
@ -166,6 +204,48 @@ func resourceDigitalOceanLoadbalancer() *schema.Resource {
|
||||||
Computed: true,
|
Computed: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
CustomizeDiff: func(diff *schema.ResourceDiff, v interface{}) error {
|
||||||
|
|
||||||
|
if _, hasHealthCheck := diff.GetOk("healthcheck"); hasHealthCheck {
|
||||||
|
|
||||||
|
healthCheckProtocol := diff.Get("healthcheck.0.protocol").(string)
|
||||||
|
_, hasPath := diff.GetOk("healthcheck.0.path")
|
||||||
|
if healthCheckProtocol == "http" {
|
||||||
|
if !hasPath {
|
||||||
|
return fmt.Errorf("health check `path` is required for when protocol is `http`")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if hasPath {
|
||||||
|
return fmt.Errorf("health check `path` is not allowed for when protocol is `tcp`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, hasStickySession := diff.GetOk("sticky_sessions.#"); hasStickySession {
|
||||||
|
|
||||||
|
sessionType := diff.Get("sticky_sessions.0.type").(string)
|
||||||
|
_, hasCookieName := diff.GetOk("sticky_sessions.0.cookie_name")
|
||||||
|
_, hasTtlSeconds := diff.GetOk("sticky_sessions.0.cookie_ttl_seconds")
|
||||||
|
if sessionType == "cookies" {
|
||||||
|
if !hasCookieName {
|
||||||
|
return fmt.Errorf("sticky sessions `cookie_name` is required for when type is `cookie`")
|
||||||
|
}
|
||||||
|
if !hasTtlSeconds {
|
||||||
|
return fmt.Errorf("sticky sessions `cookie_ttl_seconds` is required for when type is `cookie`")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if hasCookieName {
|
||||||
|
return fmt.Errorf("sticky sessions `cookie_name` is not allowed for when type is `none`")
|
||||||
|
}
|
||||||
|
if hasTtlSeconds {
|
||||||
|
return fmt.Errorf("sticky sessions `cookie_ttl_seconds` is not allowed for when type is `none`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,23 +258,17 @@ func buildLoadBalancerRequest(d *schema.ResourceData) (*godo.LoadBalancerRequest
|
||||||
ForwardingRules: expandForwardingRules(d.Get("forwarding_rule").([]interface{})),
|
ForwardingRules: expandForwardingRules(d.Get("forwarding_rule").([]interface{})),
|
||||||
}
|
}
|
||||||
|
|
||||||
if v, ok := d.GetOk("droplet_ids"); ok {
|
if v, ok := d.GetOk("droplet_tag"); ok {
|
||||||
|
opts.Tag = v.(string)
|
||||||
|
} else if v, ok := d.GetOk("droplet_ids"); ok {
|
||||||
var droplets []int
|
var droplets []int
|
||||||
for _, id := range v.([]interface{}) {
|
for _, id := range v.(*schema.Set).List() {
|
||||||
i, err := strconv.Atoi(id.(string))
|
droplets = append(droplets, id.(int))
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
droplets = append(droplets, i)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.DropletIDs = droplets
|
opts.DropletIDs = droplets
|
||||||
}
|
}
|
||||||
|
|
||||||
if v, ok := d.GetOk("droplet_tag"); ok {
|
|
||||||
opts.Tag = v.(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
if v, ok := d.GetOk("healthcheck"); ok {
|
if v, ok := d.GetOk("healthcheck"); ok {
|
||||||
opts.HealthCheck = expandHealthCheck(v.([]interface{}))
|
opts.HealthCheck = expandHealthCheck(v.([]interface{}))
|
||||||
}
|
}
|
||||||
|
@ -288,6 +362,8 @@ func resourceDigitalOceanLoadbalancerUpdate(d *schema.ResourceData, meta interfa
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Printf("UIIIII: %v", lbOpts)
|
||||||
|
|
||||||
log.Printf("[DEBUG] Load Balancer Update: %#v", lbOpts)
|
log.Printf("[DEBUG] Load Balancer Update: %#v", lbOpts)
|
||||||
_, _, err = client.LoadBalancers.Update(context.Background(), d.Id(), lbOpts)
|
_, _, err = client.LoadBalancers.Update(context.Background(), d.Id(), lbOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -42,6 +42,53 @@ resource "digitalocean_loadbalancer" "public" {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
When managing certificates attached to the load balancer, make sure to add the `create_before_destroy`
|
||||||
|
lifecycle property in order to ensure the certificate is correctly updated when changed. The order of
|
||||||
|
operations will then be: `Create new certificate` -> `Update loadbalancer with new certificate` ->
|
||||||
|
`Delete old certificate`. When doing so, you must also change the name of the certificate,
|
||||||
|
as there cannot be multiple certificates with the same name in an account.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
resource "digitalocean_certificate" "cert" {
|
||||||
|
name = "cert"
|
||||||
|
private_key = "${file("key.pem")}"
|
||||||
|
leaf_certificate = "${file("cert.pem")}"
|
||||||
|
|
||||||
|
lifecycle {
|
||||||
|
create_before_destroy = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "digitalocean_droplet" "web" {
|
||||||
|
name = "web-1"
|
||||||
|
size = "s-1vcpu-1gb"
|
||||||
|
image = "centos-7-x64"
|
||||||
|
region = "nyc3"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "digitalocean_loadbalancer" "public" {
|
||||||
|
name = "loadbalancer-1"
|
||||||
|
region = "nyc3"
|
||||||
|
|
||||||
|
forwarding_rule {
|
||||||
|
entry_port = 443
|
||||||
|
entry_protocol = "https"
|
||||||
|
|
||||||
|
target_port = 80
|
||||||
|
target_protocol = "http"
|
||||||
|
|
||||||
|
certificate_id = "${digitalocean_certificate.cert.id}"
|
||||||
|
}
|
||||||
|
|
||||||
|
healthcheck {
|
||||||
|
port = 22
|
||||||
|
protocol = "tcp"
|
||||||
|
}
|
||||||
|
|
||||||
|
droplet_ids = ["${digitalocean_droplet.web.id}"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Argument Reference
|
## Argument Reference
|
||||||
|
|
||||||
The following arguments are supported:
|
The following arguments are supported:
|
||||||
|
|
Loading…
Reference in New Issue