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

feat: support skip_destroy on aws_cloudwatch_logs_group resource #26775

Merged
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/26775.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_cloudwatch_logs_group: Add `skip_destroy` argument
```
263 changes: 135 additions & 128 deletions internal/service/logs/group.go
Original file line number Diff line number Diff line change
@@ -1,31 +1,42 @@
package logs

import (
"fmt"
"context"
"log"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
"github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"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"
"github.com/hashicorp/terraform-provider-aws/internal/create"
tftags "github.com/hashicorp/terraform-provider-aws/internal/tags"
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
"github.com/hashicorp/terraform-provider-aws/internal/verify"
)

func ResourceGroup() *schema.Resource {
return &schema.Resource{
Create: resourceGroupCreate,
Read: resourceGroupRead,
Update: resourceGroupUpdate,
Delete: resourceGroupDelete,
CreateWithoutTimeout: resourceGroupCreate,
ReadWithoutTimeout: resourceGroupRead,
UpdateWithoutTimeout: resourceGroupUpdate,
DeleteWithoutTimeout: resourceGroupDelete,

Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"arn": {
Type: schema.TypeString,
Computed: true,
},
"kms_key_id": {
Type: schema.TypeString,
Optional: true,
},
"name": {
Type: schema.TypeString,
Optional: true,
Expand All @@ -35,29 +46,24 @@ func ResourceGroup() *schema.Resource {
ValidateFunc: validLogGroupName,
},
"name_prefix": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validLogGroupNamePrefix,
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ConflictsWith: []string{"name"},
ValidateFunc: validLogGroupNamePrefix,
},

"retention_in_days": {
Type: schema.TypeInt,
Optional: true,
Default: 0,
ValidateFunc: validation.IntInSlice([]int{0, 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653}),
},

"kms_key_id": {
Type: schema.TypeString,
"skip_destroy": {
Type: schema.TypeBool,
Default: false,
Optional: true,
},

"arn": {
Type: schema.TypeString,
Computed: true,
},

"tags": tftags.TagsSchema(),
"tags_all": tftags.TagsSchemaComputed(),
},
Expand All @@ -66,197 +72,198 @@ func ResourceGroup() *schema.Resource {
}
}

func resourceGroupCreate(d *schema.ResourceData, meta interface{}) error {
func resourceGroupCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
conn := meta.(*conns.AWSClient).LogsConn
defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig
tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{})))

var logGroupName string
if v, ok := d.GetOk("name"); ok {
logGroupName = v.(string)
} else if v, ok := d.GetOk("name_prefix"); ok {
logGroupName = resource.PrefixedUniqueId(v.(string))
} else {
logGroupName = resource.UniqueId()
}

log.Printf("[DEBUG] Creating CloudWatch Log Group: %s", logGroupName)

params := &cloudwatchlogs.CreateLogGroupInput{
LogGroupName: aws.String(logGroupName),
name := create.Name(d.Get("name").(string), d.Get("name_prefix").(string))
input := &cloudwatchlogs.CreateLogGroupInput{
LogGroupName: aws.String(name),
}

if v, ok := d.GetOk("kms_key_id"); ok {
params.KmsKeyId = aws.String(v.(string))
input.KmsKeyId = aws.String(v.(string))
}

if len(tags) > 0 {
params.Tags = Tags(tags.IgnoreAWS())
input.Tags = Tags(tags.IgnoreAWS())
}

_, err := conn.CreateLogGroup(params)

if tfawserr.ErrCodeEquals(err, cloudwatchlogs.ErrCodeResourceAlreadyExistsException) {
return fmt.Errorf("Creating CloudWatch Log Group failed: %s: The CloudWatch Log Group '%s' already exists.", err, d.Get("name").(string))
}
_, err := conn.CreateLogGroupWithContext(ctx, input)

if err != nil {
return fmt.Errorf("Creating CloudWatch Log Group failed: %s '%s'", err, d.Get("name"))
return diag.Errorf("creating CloudWatch Logs Log Group (%s): %s", name, err)
}

d.SetId(logGroupName)

log.Println("[INFO] CloudWatch Log Group created")
d.SetId(name)

if v, ok := d.GetOk("retention_in_days"); ok {
input := cloudwatchlogs.PutRetentionPolicyInput{
LogGroupName: aws.String(logGroupName),
_, err := conn.PutRetentionPolicyWithContext(ctx, &cloudwatchlogs.PutRetentionPolicyInput{
LogGroupName: aws.String(d.Id()),
RetentionInDays: aws.Int64(int64(v.(int))),
}
log.Printf("[DEBUG] Setting retention for CloudWatch Log Group: %q: %s", logGroupName, input)
_, err = conn.PutRetentionPolicy(&input)
})

if err != nil {
return err
return diag.Errorf("setting CloudWatch Logs Log Group (%s) retention policy: %s", d.Id(), err)
}
}

return resourceGroupRead(d, meta)
return resourceGroupRead(ctx, d, meta)
}

func resourceGroupRead(d *schema.ResourceData, meta interface{}) error {
func resourceGroupRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
conn := meta.(*conns.AWSClient).LogsConn
defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig
ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig

log.Printf("[DEBUG] Reading CloudWatch Log Group: %q", d.Get("name").(string))
lg, err := LookupGroup(conn, d.Id())
if err != nil {
return err
}
lg, err := FindLogGroupByName(ctx, conn, d.Id())

if lg == nil {
log.Printf("[DEBUG] CloudWatch Group %q Not Found", d.Id())
if !d.IsNewResource() && tfresource.NotFound(err) {
log.Printf("[WARN] CloudWatch Logs Log Group (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}

if err != nil {
return diag.Errorf("reading CloudWatch Logs Log Group (%s): %s", d.Id(), err)
}

d.Set("arn", TrimLogGroupARNWildcardSuffix(aws.StringValue(lg.Arn)))
d.Set("name", lg.LogGroupName)
d.Set("kms_key_id", lg.KmsKeyId)
d.Set("name", lg.LogGroupName)
d.Set("name_prefix", create.NamePrefixFromName(aws.StringValue(lg.LogGroupName)))
d.Set("retention_in_days", lg.RetentionInDays)

tags, err := ListTags(conn, d.Id())
tags, err := ListTagsWithContext(ctx, conn, d.Id())

if err != nil {
return fmt.Errorf("listing tags for CloudWatch Logs Group (%s): %s", d.Id(), err)
return diag.Errorf("listing tags for CloudWatch Logs Log Group (%s): %s", d.Id(), err)
}

tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig)

//lintignore:AWSR002
if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil {
return fmt.Errorf("setting tags: %w", err)
return diag.Errorf("setting tags: %s", err)
}

if err := d.Set("tags_all", tags.Map()); err != nil {
return fmt.Errorf("setting tags_all: %w", err)
return diag.Errorf("setting tags_all: %s", err)
}

return nil
}

func LookupGroup(conn *cloudwatchlogs.CloudWatchLogs, name string) (*cloudwatchlogs.LogGroup, error) {
input := &cloudwatchlogs.DescribeLogGroupsInput{
LogGroupNamePrefix: aws.String(name),
}
var logGroup *cloudwatchlogs.LogGroup
err := conn.DescribeLogGroupsPages(input, func(page *cloudwatchlogs.DescribeLogGroupsOutput, lastPage bool) bool {
for _, lg := range page.LogGroups {
if aws.StringValue(lg.LogGroupName) == name {
logGroup = lg
return false
}
}
return !lastPage
})
if err != nil {
return nil, err
}

return logGroup, nil
}

func resourceGroupUpdate(d *schema.ResourceData, meta interface{}) error {
func resourceGroupUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
conn := meta.(*conns.AWSClient).LogsConn

name := d.Id()
log.Printf("[DEBUG] Updating CloudWatch Log Group: %q", name)

if d.HasChange("retention_in_days") {
var err error

if v, ok := d.GetOk("retention_in_days"); ok {
input := cloudwatchlogs.PutRetentionPolicyInput{
LogGroupName: aws.String(name),
_, err := conn.PutRetentionPolicyWithContext(ctx, &cloudwatchlogs.PutRetentionPolicyInput{
LogGroupName: aws.String(d.Id()),
RetentionInDays: aws.Int64(int64(v.(int))),
})

if err != nil {
return diag.Errorf("setting CloudWatch Logs Log Group (%s) retention policy: %s", d.Id(), err)
}
log.Printf("[DEBUG] Setting retention for CloudWatch Log Group: %q: %s", name, input)
_, err = conn.PutRetentionPolicy(&input)
} else {
log.Printf("[DEBUG] Deleting retention for CloudWatch Log Group: %q", name)
_, err = conn.DeleteRetentionPolicy(&cloudwatchlogs.DeleteRetentionPolicyInput{
LogGroupName: aws.String(name),
_, err := conn.DeleteRetentionPolicyWithContext(ctx, &cloudwatchlogs.DeleteRetentionPolicyInput{
LogGroupName: aws.String(d.Id()),
})
}

if err != nil {
return err
}
}

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

if err := UpdateTags(conn, d.Id(), o, n); err != nil {
return fmt.Errorf("updating CloudWatch Log Group (%s) tags: %s", d.Id(), err)
if err != nil {
return diag.Errorf("deleting CloudWatch Logs Log Group (%s) retention policy: %s", d.Id(), err)
}
}
}

if d.HasChange("kms_key_id") && !d.IsNewResource() {
_, newKey := d.GetChange("kms_key_id")

if newKey.(string) == "" {
_, err := conn.DisassociateKmsKey(&cloudwatchlogs.DisassociateKmsKeyInput{
LogGroupName: aws.String(name),
if d.HasChange("kms_key_id") {
if v, ok := d.GetOk("kms_key_id"); ok {
_, err := conn.AssociateKmsKeyWithContext(ctx, &cloudwatchlogs.AssociateKmsKeyInput{
KmsKeyId: aws.String(v.(string)),
LogGroupName: aws.String(d.Id()),
})

if err != nil {
return err
return diag.Errorf("associating CloudWatch Logs Log Group (%s) KMS key: %s", d.Id(), err)
}
} else {
_, err := conn.AssociateKmsKey(&cloudwatchlogs.AssociateKmsKeyInput{
LogGroupName: aws.String(name),
KmsKeyId: aws.String(newKey.(string)),
_, err := conn.DisassociateKmsKeyWithContext(ctx, &cloudwatchlogs.DisassociateKmsKeyInput{
LogGroupName: aws.String(d.Id()),
})

if err != nil {
return err
return diag.Errorf("disassociating CloudWatch Logs Log Group (%s) KMS key: %s", d.Id(), err)
}
}
}

return resourceGroupRead(d, meta)
if d.HasChange("tags_all") {
o, n := d.GetChange("tags_all")

if err := UpdateTagsWithContext(ctx, conn, d.Id(), o, n); err != nil {
return diag.Errorf("updating CloudWatch Logs Log Group (%s) tags: %s", d.Id(), err)
}
}

return resourceGroupRead(ctx, d, meta)
}

func resourceGroupDelete(d *schema.ResourceData, meta interface{}) error {
func resourceGroupDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
if v, ok := d.GetOk("skip_destroy"); ok && v.(bool) {
log.Printf("[DEBUG] Retaining CloudWatch Logs Log Group: %s", d.Id())
return nil
}

conn := meta.(*conns.AWSClient).LogsConn
log.Printf("[INFO] Deleting CloudWatch Log Group: %s", d.Id())
_, err := conn.DeleteLogGroup(&cloudwatchlogs.DeleteLogGroupInput{
LogGroupName: aws.String(d.Get("name").(string)),

log.Printf("[INFO] Deleting CloudWatch Logs Log Group: %s", d.Id())
_, err := conn.DeleteLogGroupWithContext(ctx, &cloudwatchlogs.DeleteLogGroupInput{
LogGroupName: aws.String(d.Id()),
})

if tfawserr.ErrCodeEquals(err, cloudwatchlogs.ErrCodeResourceNotFoundException) {
return nil
}

if err != nil {
return fmt.Errorf("Error deleting CloudWatch Log Group: %s", err)
return diag.Errorf("deleting CloudWatch Logs Log Group (%s): %s", d.Id(), err)
}
log.Println("[INFO] CloudWatch Log Group deleted")

return nil
}

func FindLogGroupByName(ctx context.Context, conn *cloudwatchlogs.CloudWatchLogs, name string) (*cloudwatchlogs.LogGroup, error) {
input := &cloudwatchlogs.DescribeLogGroupsInput{
LogGroupNamePrefix: aws.String(name),
}
var output *cloudwatchlogs.LogGroup

err := conn.DescribeLogGroupsPagesWithContext(ctx, input, func(page *cloudwatchlogs.DescribeLogGroupsOutput, lastPage bool) bool {
if page == nil {
return !lastPage
}

for _, v := range page.LogGroups {
if aws.StringValue(v.LogGroupName) == name {
output = v

return false
}
}

return !lastPage
})

if err != nil {
return nil, err
}

if output == nil {
return nil, tfresource.NewEmptyResultError(input)
}

return output, nil
}
Loading