Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Lightsail load balancer resource #11405

Merged
merged 20 commits into from Oct 20, 2022
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions aws/provider.go
Expand Up @@ -795,6 +795,7 @@ func Provider() *schema.Provider {
"aws_lightsail_domain": resourceAwsLightsailDomain(),
"aws_lightsail_instance": resourceAwsLightsailInstance(),
"aws_lightsail_key_pair": resourceAwsLightsailKeyPair(),
"aws_lightsail_load_balancer": resourceAwsLightsailLoadBalancer(),
"aws_lightsail_static_ip": resourceAwsLightsailStaticIp(),
"aws_lightsail_static_ip_attachment": resourceAwsLightsailStaticIpAttachment(),
"aws_lb_cookie_stickiness_policy": resourceAwsLBCookieStickinessPolicy(),
Expand Down
2 changes: 1 addition & 1 deletion aws/resource_aws_lightsail_instance_test.go
Expand Up @@ -193,7 +193,7 @@ func TestAccAWSLightsailInstance_Tags(t *testing.T) {
})
}

func TestAccAWSLightsailInstance_disapear(t *testing.T) {
func TestAccAWSLightsailInstance_disappear(t *testing.T) {
var conf lightsail.Instance
lightsailName := fmt.Sprintf("tf-test-lightsail-%d", acctest.RandInt())

Expand Down
239 changes: 239 additions & 0 deletions aws/resource_aws_lightsail_load_balancer.go
@@ -0,0 +1,239 @@
package aws

import (
"fmt"
"log"
"regexp"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/lightsail"
"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"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags"
)

func resourceAwsLightsailLoadBalancer() *schema.Resource {
return &schema.Resource{
Create: resourceAwsLightsailLoadBalancerCreate,
Read: resourceAwsLightsailLoadBalancerRead,
Update: resourceAwsLightsailLoadBalancerUpdate,
Delete: resourceAwsLightsailLoadBalancerDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.All(
validation.StringLenBetween(2, 255),
validation.StringMatch(regexp.MustCompile(`^[a-zA-Z]`), "must begin with an alphabetic character"),
validation.StringMatch(regexp.MustCompile(`^[a-zA-Z0-9_\-.]+[^._\-]$`), "must contain only alphanumeric characters, underscores, hyphens, and dots"),
),
},
"health_check_path": {
Type: schema.TypeString,
Optional: true,
Default: "/",
},
"instance_port": {
Type: schema.TypeInt,
Required: true,
ForceNew: true,
ValidateFunc: validation.IntBetween(0, 65535),
},
"tags": tagsSchema(),
"arn": {
Type: schema.TypeString,
Computed: true,
},
"created_at": {
Type: schema.TypeString,
Computed: true,
},
"dns_name": {
Type: schema.TypeString,
Computed: true,
},
"protocol": {
Type: schema.TypeString,
Computed: true,
},
"public_ports": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeInt},
},
},
}
}

func resourceAwsLightsailLoadBalancerCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).lightsailconn

req := lightsail.CreateLoadBalancerInput{
HealthCheckPath: aws.String(d.Get("health_check_path").(string)),
InstancePort: aws.Int64(int64(d.Get("instance_port").(int))),
LoadBalancerName: aws.String(d.Get("name").(string)),
}

if v := d.Get("tags").(map[string]interface{}); len(v) > 0 {
req.Tags = keyvaluetags.New(v).IgnoreAws().LightsailTags()
}

resp, err := conn.CreateLoadBalancer(&req)
if err != nil {
return err
}

if len(resp.Operations) == 0 {
return fmt.Errorf("No operations found for CreateInstance request")
}

op := resp.Operations[0]
d.SetId(d.Get("name").(string))

stateConf := &resource.StateChangeConf{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we move this to an internal waiter function? see https://github.com/hashicorp/terraform-provider-aws/tree/main/aws/internal/service/glue for example

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback, this change will be implemented once the following pull request is merged by @akonrath akonrath#96

Pending: []string{"Started"},
Target: []string{"Completed", "Succeeded"},
Refresh: resourceAwsLightsailLoadBalancerOperationRefreshFunc(op.Id, meta),
Timeout: 10 * time.Minute,
Delay: 5 * time.Second,
MinTimeout: 3 * time.Second,
}

_, err = stateConf.WaitForState()
if err != nil {
// We don't return an error here because the Create call succeeded
log.Printf("[ERR] Error waiting for load balancer (%s) to become ready: %s", d.Id(), err)
}

return resourceAwsLightsailLoadBalancerRead(d, meta)
}

func resourceAwsLightsailLoadBalancerRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).lightsailconn
ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig

resp, err := conn.GetLoadBalancer(&lightsail.GetLoadBalancerInput{
LoadBalancerName: aws.String(d.Id()),
})

if err != nil {
if awsErr, ok := err.(awserr.Error); ok {
if awsErr.Code() == "NotFoundException" {
log.Printf("[WARN] Lightsail load balancer (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}
return err
}
return err
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lb := resp.LoadBalancer

and reuse it for all others instead repeating it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback, this change will be implemented once the following pull request is merged by @akonrath akonrath#96

d.Set("arn", resp.LoadBalancer.Arn)
d.Set("created_at", resp.LoadBalancer.CreatedAt.Format(time.RFC3339))
d.Set("health_check_path", resp.LoadBalancer.HealthCheckPath)
d.Set("instance_port", resp.LoadBalancer.InstancePort)
d.Set("name", resp.LoadBalancer.Name)
d.Set("protocol", resp.LoadBalancer.Protocol)
d.Set("public_ports", resp.LoadBalancer.PublicPorts)
d.Set("dns_name", resp.LoadBalancer.DnsName)

if err := d.Set("tags", keyvaluetags.LightsailKeyValueTags(resp.LoadBalancer.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil {
return fmt.Errorf("error setting tags: %s", err)
}

return nil
}

func resourceAwsLightsailLoadBalancerDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).lightsailconn
resp, err := conn.DeleteLoadBalancer(&lightsail.DeleteLoadBalancerInput{
LoadBalancerName: aws.String(d.Id()),
})

op := resp.Operations[0]

if err != nil {
return err
}

stateConf := &resource.StateChangeConf{
Pending: []string{"Started"},
Target: []string{"Completed", "Succeeded"},
Refresh: resourceAwsLightsailLoadBalancerOperationRefreshFunc(op.Id, meta),
Timeout: 10 * time.Minute,
Delay: 5 * time.Second,
MinTimeout: 3 * time.Second,
}

_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf(
"Error waiting for load balancer (%s) to become destroyed: %s",
d.Id(), err)
}

return err
}

func resourceAwsLightsailLoadBalancerUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).lightsailconn

if d.HasChange("health_check_path") {
_, err := conn.UpdateLoadBalancerAttribute(&lightsail.UpdateLoadBalancerAttributeInput{
AttributeName: aws.String("HealthCheckPath"),
AttributeValue: aws.String(d.Get("health_check_path").(string)),
LoadBalancerName: aws.String(d.Get("name").(string)),
})
d.Set("health_check_path", d.Get("health_check_path").(string))
if err != nil {
return err
}
}

if d.HasChange("tags") {
o, n := d.GetChange("tags")

if err := keyvaluetags.LightsailUpdateTags(conn, d.Id(), o, n); err != nil {
return fmt.Errorf("error updating Lightsail Instance (%s) tags: %s", d.Id(), err)
}
}

return resourceAwsLightsailLoadBalancerRead(d, meta)
}

// method to check the status of an Operation, which is returned from
// Create/Delete methods.
// Status's are an aws.OperationStatus enum:
// - NotStarted
// - Started
// - Failed
// - Completed
// - Succeeded (not documented?)
func resourceAwsLightsailLoadBalancerOperationRefreshFunc(
oid *string, meta interface{}) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
conn := meta.(*AWSClient).lightsailconn
log.Printf("[DEBUG] Checking if Lightsail Operation (%s) is Completed", *oid)
o, err := conn.GetOperation(&lightsail.GetOperationInput{
OperationId: oid,
})
if err != nil {
return o, "FAILED", err
}

if o.Operation == nil {
return nil, "Failed", fmt.Errorf("Error retrieving Operation info for operation (%s)", *oid)
}

log.Printf("[DEBUG] Lightsail Operation (%s) is currently %q", *oid, *o.Operation.Status)
return o, *o.Operation.Status, nil
}
}