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

r/cloud9_environment_ec2 - validations + refactor to use finder and waiter packages + sweeper #18560

Merged
merged 7 commits into from
Jan 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .changelog/18560.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_cloud9_environment_ec2: Add plan time validations for `name`, `automatic_stop_time_minutes`, `description`.
```
167 changes: 59 additions & 108 deletions internal/service/cloud9/environment_ec2.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ package cloud9
import (
"fmt"
"log"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/cloud9"
"github.com/hashicorp/aws-sdk-go-base/tfawserr"
"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/hashicorp/terraform-provider-aws/internal/conns"
tfiam "github.com/hashicorp/terraform-provider-aws/internal/service/iam"
tftags "github.com/hashicorp/terraform-provider-aws/internal/tags"
Expand All @@ -30,22 +30,25 @@ func ResourceEnvironmentEC2() *schema.Resource {

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringLenBetween(1, 60),
},
"instance_type": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"automatic_stop_time_minutes": {
Type: schema.TypeInt,
Optional: true,
ForceNew: true,
Type: schema.TypeInt,
Optional: true,
ForceNew: true,
ValidateFunc: validation.IntAtMost(20160),
},
"description": {
Type: schema.TypeString,
Optional: true,
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringLenBetween(1, 200),
},
"owner_arn": {
Type: schema.TypeString,
Expand Down Expand Up @@ -80,30 +83,32 @@ func resourceEnvironmentEC2Create(d *schema.ResourceData, meta interface{}) erro
defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig
tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{})))

params := &cloud9.CreateEnvironmentEC2Input{
InstanceType: aws.String(d.Get("instance_type").(string)),
Name: aws.String(d.Get("name").(string)),
name := d.Get("name").(string)
input := &cloud9.CreateEnvironmentEC2Input{
ClientRequestToken: aws.String(resource.UniqueId()),
InstanceType: aws.String(d.Get("instance_type").(string)),
Name: aws.String(name),
Tags: Tags(tags.IgnoreAWS()),
}

if v, ok := d.GetOk("automatic_stop_time_minutes"); ok {
params.AutomaticStopTimeMinutes = aws.Int64(int64(v.(int)))
input.AutomaticStopTimeMinutes = aws.Int64(int64(v.(int)))
}
if v, ok := d.GetOk("description"); ok {
params.Description = aws.String(v.(string))
input.Description = aws.String(v.(string))
}
if v, ok := d.GetOk("owner_arn"); ok {
params.OwnerArn = aws.String(v.(string))
input.OwnerArn = aws.String(v.(string))
}
if v, ok := d.GetOk("subnet_id"); ok {
params.SubnetId = aws.String(v.(string))
input.SubnetId = aws.String(v.(string))
}

var out *cloud9.CreateEnvironmentEC2Output
log.Printf("[INFO] Creating Cloud9 EC2 Environment: %s", input)
var output *cloud9.CreateEnvironmentEC2Output
err := resource.Retry(tfiam.PropagationTimeout, func() *resource.RetryError {
var err error
out, err = conn.CreateEnvironmentEC2(params)
output, err = conn.CreateEnvironmentEC2(input)
if err != nil {
// NotFoundException: User arn:aws:iam::*******:user/****** does not exist.
if tfawserr.ErrMessageContains(err, cloud9.ErrCodeNotFoundException, "User") {
Expand All @@ -113,44 +118,21 @@ func resourceEnvironmentEC2Create(d *schema.ResourceData, meta interface{}) erro
}
return nil
})

if tfresource.TimedOut(err) {
out, err = conn.CreateEnvironmentEC2(params)
output, err = conn.CreateEnvironmentEC2(input)
}

if err != nil {
return fmt.Errorf("Error creating Cloud9 EC2 Environment: %s", err)
return fmt.Errorf("error creating Cloud9 EC2 Environment (%s): %w", name, err)
}
d.SetId(aws.StringValue(out.EnvironmentId))

stateConf := resource.StateChangeConf{
Pending: []string{
cloud9.EnvironmentStatusConnecting,
cloud9.EnvironmentStatusCreating,
},
Target: []string{
cloud9.EnvironmentStatusReady,
},
Timeout: 10 * time.Minute,
Refresh: func() (interface{}, string, error) {
out, err := conn.DescribeEnvironmentStatus(&cloud9.DescribeEnvironmentStatusInput{
EnvironmentId: aws.String(d.Id()),
})
if err != nil {
return 42, "", err
}
d.SetId(aws.StringValue(output.EnvironmentId))

status := aws.StringValue(out.Status)
_, err = waitEnvironmentReady(conn, d.Id())

if status == cloud9.EnvironmentStatusError && out.Message != nil {
return out, status, fmt.Errorf("Reason: %s", aws.StringValue(out.Message))
}

return out, status, nil
},
}
_, err = stateConf.WaitForState()
if err != nil {
return err
return fmt.Errorf("error waiting for Cloud9 EC2 Environment (%s) create: %w", d.Id(), err)
}

return resourceEnvironmentEC2Read(d, meta)
Expand All @@ -161,25 +143,17 @@ func resourceEnvironmentEC2Read(d *schema.ResourceData, meta interface{}) error
defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig
ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig

log.Printf("[INFO] Reading Cloud9 Environment EC2 %s", d.Id())
env, err := FindEnvironmentByID(conn, d.Id())

out, err := conn.DescribeEnvironments(&cloud9.DescribeEnvironmentsInput{
EnvironmentIds: []*string{aws.String(d.Id())},
})
if err != nil {
if tfawserr.ErrMessageContains(err, cloud9.ErrCodeNotFoundException, "") {
log.Printf("[WARN] Cloud9 Environment EC2 (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}
return err
}
if len(out.Environments) == 0 {
log.Printf("[WARN] Cloud9 Environment EC2 (%s) not found, removing from state", d.Id())
if !d.IsNewResource() && tfresource.NotFound(err) {
log.Printf("[WARN] Cloud9 EC2 Environment (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}
env := out.Environments[0]

if err != nil {
return fmt.Errorf("error reading Cloud9 EC2 Environment (%s): %w", d.Id(), err)
}

arn := aws.StringValue(env.Arn)
d.Set("arn", arn)
Expand All @@ -191,7 +165,7 @@ func resourceEnvironmentEC2Read(d *schema.ResourceData, meta interface{}) error
tags, err := ListTags(conn, arn)

if err != nil {
return fmt.Errorf("error listing tags for Cloud9 EC2 Environment (%s): %s", arn, err)
return fmt.Errorf("error listing tags for Cloud9 EC2 Environment (%s): %w", arn, err)
}

tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig)
Expand All @@ -205,35 +179,33 @@ func resourceEnvironmentEC2Read(d *schema.ResourceData, meta interface{}) error
return fmt.Errorf("error setting tags_all: %w", err)
}

log.Printf("[DEBUG] Received Cloud9 Environment EC2: %s", env)

return nil
}

func resourceEnvironmentEC2Update(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).Cloud9Conn

input := cloud9.UpdateEnvironmentInput{
Description: aws.String(d.Get("description").(string)),
EnvironmentId: aws.String(d.Id()),
Name: aws.String(d.Get("name").(string)),
}
if d.HasChangesExcept("tags_all", "tags") {
input := cloud9.UpdateEnvironmentInput{
Description: aws.String(d.Get("description").(string)),
EnvironmentId: aws.String(d.Id()),
Name: aws.String(d.Get("name").(string)),
}

log.Printf("[INFO] Updating Cloud9 Environment EC2: %s", input)
log.Printf("[INFO] Updating Cloud9 EC2 Environment: %s", input)
_, err := conn.UpdateEnvironment(&input)

out, err := conn.UpdateEnvironment(&input)
if err != nil {
return err
if err != nil {
return fmt.Errorf("error updating Cloud9 EC2 Environment (%s): %w", d.Id(), err)
}
}

log.Printf("[DEBUG] Cloud9 Environment EC2 updated: %s", out)

if d.HasChange("tags_all") {
o, n := d.GetChange("tags_all")
arn := d.Get("arn").(string)

if err := UpdateTags(conn, arn, o, n); err != nil {
return fmt.Errorf("error updating Cloud9 EC2 Environment (%s) tags: %s", arn, err)
return fmt.Errorf("error updating Cloud9 EC2 Environment (%s) tags: %w", arn, err)
}
}

Expand All @@ -243,45 +215,24 @@ func resourceEnvironmentEC2Update(d *schema.ResourceData, meta interface{}) erro
func resourceEnvironmentEC2Delete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).Cloud9Conn

log.Printf("[INFO] Deleting Cloud9 EC2 Environment: %s", d.Id())
_, err := conn.DeleteEnvironment(&cloud9.DeleteEnvironmentInput{
EnvironmentId: aws.String(d.Id()),
})
if err != nil {
return err
}

input := &cloud9.DescribeEnvironmentsInput{
EnvironmentIds: []*string{aws.String(d.Id())},
if tfawserr.ErrCodeEquals(err, cloud9.ErrCodeNotFoundException) {
return nil
}
var out *cloud9.DescribeEnvironmentsOutput
err = resource.Retry(20*time.Minute, func() *resource.RetryError { // Deleting instances can take a long time
out, err = conn.DescribeEnvironments(input)
if err != nil {
if tfawserr.ErrMessageContains(err, cloud9.ErrCodeNotFoundException, "") {
return nil
}
// :'-(
if tfawserr.ErrMessageContains(err, "AccessDeniedException", "is not authorized to access this resource") {
return nil
}
return resource.NonRetryableError(err)
}
if len(out.Environments) == 0 {
return nil
}
return resource.RetryableError(fmt.Errorf("Cloud9 EC2 Environment %q still exists", d.Id()))
})
if tfresource.TimedOut(err) {
out, err = conn.DescribeEnvironments(input)
if tfawserr.ErrMessageContains(err, cloud9.ErrCodeNotFoundException, "") {
return nil
}
if tfawserr.ErrMessageContains(err, "AccessDeniedException", "is not authorized to access this resource") {
return nil
}

if err != nil {
return fmt.Errorf("error deleting Cloud9 EC2 Environment (%s): %w", d.Id(), err)
}

_, err = waitEnvironmentDeleted(conn, d.Id())

if err != nil {
return fmt.Errorf("Error deleting Cloud9 EC2 Environment: %s", err)
return fmt.Errorf("error waiting for Cloud9 EC2 Environment (%s) delete: %w", d.Id(), err)
}

return nil
}
Loading