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 1 commit
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,7 @@ func Provider() terraform.ResourceProvider {
"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_domain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func TestAccAWSLightsailDomain_disappears(t *testing.T) {
})

if err != nil {
return fmt.Errorf("Error deleting Lightsail Domain in disapear test")
return fmt.Errorf("Error deleting Lightsail Domain in disappear test")
}

return nil
Expand Down
2 changes: 1 addition & 1 deletion aws/resource_aws_lightsail_instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,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
236 changes: 236 additions & 0 deletions aws/resource_aws_lightsail_load_balancer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
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/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/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
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)

if err := d.Set("tags", keyvaluetags.LightsailKeyValueTags(resp.LoadBalancer.Tags).IgnoreAws().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.SetPartial("health_check_path")
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
}
}
Loading