diff --git a/vultr/data_source_vultr_load_balancer.go b/vultr/data_source_vultr_load_balancer.go index 97098caa..8d0de966 100644 --- a/vultr/data_source_vultr_load_balancer.go +++ b/vultr/data_source_vultr_load_balancer.go @@ -82,6 +82,10 @@ func dataSourceVultrLoadBalancer() *schema.Resource { Computed: true, Elem: &schema.Schema{Type: schema.TypeMap}, }, + "auto_ssl_domain": { + Type: schema.TypeString, + Computed: true, + }, }, } } @@ -143,6 +147,9 @@ func dataSourceVultrLoadBalancerRead(ctx context.Context, d *schema.ResourceData if err := d.Set("ssl_redirect", lbList[0].GenericInfo.SSLRedirect); err != nil { return diag.Errorf("unable to set load_balancer `ssl_redirect` read value: %v", err) } + if err := d.Set("auto_ssl_domain", lbList[0].AutoSSL.Domain); err != nil { + return diag.Errorf("unable to set load_balancer `auto_ssl_domain` read value: %v", err) + } if err := d.Set("proxy_protocol", lbList[0].GenericInfo.ProxyProtocol); err != nil { return diag.Errorf("unable to set load_balancer `proxy_protocol` read value: %v", err) } @@ -212,6 +219,5 @@ func dataSourceVultrLoadBalancerRead(ctx context.Context, d *schema.ResourceData if err := d.Set("firewall_rules", fwrRules); err != nil { return diag.Errorf("unable to set load_balancer `firewall_rules` read value: %v", err) } - return nil } diff --git a/vultr/resource_vultr_load_balancer.go b/vultr/resource_vultr_load_balancer.go index 51290fc9..85fa8c0c 100644 --- a/vultr/resource_vultr_load_balancer.go +++ b/vultr/resource_vultr_load_balancer.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log" + "net/url" "strings" "time" @@ -199,6 +200,10 @@ func resourceVultrLoadBalancer() *schema.Resource { Type: schema.TypeBool, Computed: true, }, + "auto_ssl_domain": { + Type: schema.TypeString, + Optional: true, + }, "attached_instances": { Type: schema.TypeList, Optional: true, @@ -255,6 +260,16 @@ func resourceVultrLoadBalancerCreate(ctx context.Context, d *schema.ResourceData ssl = nil } + var autoSSL *govultr.AutoSSL + if autoSSLDomainData, autoSSLDomainOk := d.GetOk("auto_ssl_domain"); autoSSLDomainOk { + domain := autoSSLDomainData.(string) + if autoSSLDomain, err := generateAutoSSL(domain); err != nil { + return diag.Errorf("failed to parse auto SSL domain: %v", err) + } else { + autoSSL = autoSSLDomain + } + } + cookieName, cookieOk := d.GetOk("cookie_name") stickySessions := &govultr.StickySessions{} if cookieOk { @@ -278,6 +293,7 @@ func resourceVultrLoadBalancerCreate(ctx context.Context, d *schema.ResourceData StickySessions: stickySessions, ForwardingRules: fwMap, SSL: ssl, + AutoSSL: autoSSL, SSLRedirect: govultr.BoolToBoolPtr(d.Get("ssl_redirect").(bool)), ProxyProtocol: govultr.BoolToBoolPtr(d.Get("proxy_protocol").(bool)), BalancingAlgorithm: d.Get("balancing_algorithm").(string), @@ -357,7 +373,9 @@ func resourceVultrLoadBalancerRead(ctx context.Context, d *schema.ResourceData, "healthy_threshold": lb.HealthCheck.HealthyThreshold, } hc = append(hc, hcInfo) - + if err := d.Set("auto_ssl_domain", lb.AutoSSL.Domain); err != nil { + return diag.Errorf("unable to set resource load_balancer `auto_ssl_domain` read value: %v", err) + } if err := d.Set("health_check", hc); err != nil { return diag.Errorf("unable to set resource load_balancer `health_check` read value: %v", err) } @@ -397,7 +415,6 @@ func resourceVultrLoadBalancerRead(ctx context.Context, d *schema.ResourceData, if err := d.Set("vpc", lb.GenericInfo.VPC); err != nil { return diag.Errorf("unable to set resource load_balancer `vpc` read value: %v", err) } - return nil } @@ -426,7 +443,20 @@ func resourceVultrLoadBalancerUpdate(ctx context.Context, d *schema.ResourceData req.SSL = nil } } - + if d.HasChange("auto_ssl_domain") { + if autoSSLData, ok := d.GetOk("auto_ssl_domain"); ok && autoSSLData.(string) != "" { + autoSSL, err := generateAutoSSL(autoSSLData.(string)) + if err != nil { + return diag.Errorf("failed to parse auto SSL domain: %v", err) + } + req.AutoSSL = autoSSL + } else { + log.Printf("[INFO] Disabled load balancer auto SSL certificate (%v)", d.Id()) + if err := client.LoadBalancer.DeleteAutoSSL(ctx, d.Id()); err != nil { + return diag.Errorf("error disabling load balancer auto SSL certificate (%v): %v", d.Id(), err) + } + } + } if d.HasChange("forwarding_rules") { _, newFR := d.GetChange("forwarding_rules") @@ -622,3 +652,28 @@ func generateSSL(sslData interface{}) *govultr.SSL { Chain: config["chain"].(string), } } + +func generateAutoSSL(domain string) (*govultr.AutoSSL, error) { + parsedDomain, err := url.Parse(domain) + if err != nil || parsedDomain.Scheme != "" { + return nil, fmt.Errorf("invalid domain %q: must not include URL scheme (http:// or https://)", domain) + } + + hostname := parsedDomain.Hostname() + if hostname == "" { + hostname = domain + } + + parts := strings.Split(hostname, ".") + if len(parts) < 2 { + return nil, fmt.Errorf("invalid domain %q: must contain a dot (example.com)", domain) + } + + domainParts := parts[len(parts)-2:] + subParts := parts[:len(parts)-2] + + return &govultr.AutoSSL{ + DomainZone: strings.Join(domainParts, "."), + DomainSub: strings.Join(subParts, "."), + }, nil +} diff --git a/website/docs/d/load_balancer.html.markdown b/website/docs/d/load_balancer.html.markdown index 6602640e..2f8b8920 100644 --- a/website/docs/d/load_balancer.html.markdown +++ b/website/docs/d/load_balancer.html.markdown @@ -45,6 +45,7 @@ The following attributes are exported: * `cookie_name` - Name for your given sticky session. * `ssl_redirect` - Boolean value that indicates if HTTP calls will be redirected to HTTPS. * `has_ssl` - Boolean value that indicates if SSL is enabled. +* `auto_ssl_domain` - The auto SSL domain configuration for a load balancer. This can be a root domain (example.com) or include a subdomain (sub.example.com). * `attached_instances` - Array of instances that are currently attached to the load balancer. * `status` - Current status for the load balancer * `ipv4` - IPv4 address for your load balancer. @@ -75,3 +76,4 @@ The following attributes are exported: * `frontend_port` - (Required) Port on load balancer side. * `ip_type` - (Required) The type of ip this rule is - may be either v4 or v6. * `source` - (Required) IP address with subnet that is allowed through the firewall. You may also pass in `cloudflare` which will allow only CloudFlares IP range. + diff --git a/website/docs/r/load_balancer.html.markdown b/website/docs/r/load_balancer.html.markdown index eeb9b29b..5a7ad017 100644 --- a/website/docs/r/load_balancer.html.markdown +++ b/website/docs/r/load_balancer.html.markdown @@ -53,6 +53,7 @@ The follow arguments are supported: * `attached_instances` - (Optional) Array of instances that are currently attached to the load balancer. * `health_check` - (Optional) A block that defines the way load balancers should check for health. The configuration of a `health_check` is listed below. * `ssl` - (Optional) A block that supplies your ssl configuration to be used with HTTPS. The configuration of a `ssl` is listed below. +* `auto_ssl_domain` - (Optional) The auto SSL domain configuration for a load balancer. This can be a root domain (example.com) or include a subdomain (sub.example.com). * `private_network` (Optional) (Deprecated: use `vpc` instead) A private network ID that the load balancer should be attached to. * `vpc` (Optional)- A VPC ID that the load balancer should be attached to. @@ -95,6 +96,7 @@ The following attributes are exported: * `cookie_name` - Name for your given sticky session. * `ssl_redirect` - Boolean value that indicates if HTTP calls will be redirected to HTTPS. * `has_ssl` - Boolean value that indicates if SSL is enabled. +* `auto_ssl_domain` - The auto SSL domain configuration for a load balancer. * `attached_instances` - Array of instances that are currently attached to the load balancer. * `status` - Current status for the load balancer * `ipv4` - IPv4 address for your load balancer.