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/aws_backup_report_plan: Wait for correct DeploymentStatus after lifecycle operations #23967

Merged
merged 5 commits into from
Mar 31, 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/23967.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
resource/aws_backup_report_plan: Wait for asynchronous lifecycle operations to complete
```
37 changes: 37 additions & 0 deletions internal/service/backup/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,40 @@ const (
frameworkStatusFailed = "FAILED"
frameworkStatusUpdateInProgress = "UPDATE_IN_PROGRESS"
)

const (
reportPlanDeploymentStatusCompleted = "COMPLETED"
reportPlanDeploymentStatusCreateInProgress = "CREATE_IN_PROGRESS"
reportPlanDeploymentStatusDeleteInProgress = "DELETE_IN_PROGRESS"
reportPlanDeploymentStatusUpdateInProgress = "UPDATE_IN_PROGRESS"
)

const (
reportDeliveryChannelFormatCSV = "CSV"
reportDeliveryChannelFormatJSON = "JSON"
)

func reportDeliveryChannelFormat_Values() []string {
return []string{
reportDeliveryChannelFormatCSV,
reportDeliveryChannelFormatJSON,
}
}

const (
reportSettingTemplateBackupJobReport = "BACKUP_JOB_REPORT"
reportSettingTemplateControlComplianceReport = "CONTROL_COMPLIANCE_REPORT"
reportSettingTemplateCopyJobReport = "COPY_JOB_REPORT"
reportSettingTemplateResourceComplianceReport = "RESOURCE_COMPLIANCE_REPORT"
reportSettingTemplateRestoreJobReport = "RESTORE_JOB_REPORT"
)

func reportSettingTemplate_Values() []string {
return []string{
reportSettingTemplateBackupJobReport,
reportSettingTemplateControlComplianceReport,
reportSettingTemplateCopyJobReport,
reportSettingTemplateResourceComplianceReport,
reportSettingTemplateRestoreJobReport,
}
}
126 changes: 99 additions & 27 deletions internal/service/backup/report_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,8 @@ func ResourceReportPlan() *schema.Resource {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{
"CSV",
"JSON",
}, false),
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice(reportDeliveryChannelFormat_Values(), false),
},
},
"s3_bucket_name": {
Expand Down Expand Up @@ -100,16 +97,10 @@ func ResourceReportPlan() *schema.Resource {
},
// A report plan template cannot be updated
"report_template": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice([]string{
"RESOURCE_COMPLIANCE_REPORT",
"CONTROL_COMPLIANCE_REPORT",
"BACKUP_JOB_REPORT",
"COPY_JOB_REPORT",
"RESTORE_JOB_REPORT",
}, false),
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice(reportSettingTemplate_Values(), false),
},
},
},
Expand All @@ -128,7 +119,6 @@ func resourceReportPlanCreate(d *schema.ResourceData, meta interface{}) error {
tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{})))

name := d.Get("name").(string)

input := &backup.CreateReportPlanInput{
IdempotencyToken: aws.String(resource.UniqueId()),
ReportDeliveryChannel: expandReportDeliveryChannel(d.Get("report_delivery_channel").([]interface{})),
Expand All @@ -144,14 +134,19 @@ func resourceReportPlanCreate(d *schema.ResourceData, meta interface{}) error {
input.ReportPlanTags = Tags(tags.IgnoreAWS())
}

log.Printf("[DEBUG] Creating Backup Report Plan: %#v", input)
resp, err := conn.CreateReportPlan(input)
log.Printf("[DEBUG] Creating Backup Report Plan: %s", input)
output, err := conn.CreateReportPlan(input)

if err != nil {
return fmt.Errorf("error creating Backup Report Plan: %w", err)
return fmt.Errorf("error creating Backup Report Plan (%s): %w", name, err)
}

// Set ID with the name since the name is unique for the report plan
d.SetId(aws.StringValue(resp.ReportPlanName))
// Set ID with the name since the name is unique for the report plan.
d.SetId(aws.StringValue(output.ReportPlanName))

if _, err := waitReportPlanCreated(conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil {
return fmt.Errorf("error waiting for Backup Report Plan (%s) create: %w", d.Id(), err)
}

return resourceReportPlanRead(d, meta)
}
Expand Down Expand Up @@ -210,7 +205,7 @@ func resourceReportPlanRead(d *schema.ResourceData, meta interface{}) error {
func resourceReportPlanUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).BackupConn

if d.HasChanges("description", "report_delivery_channel", "report_plan_description", "report_setting") {
if d.HasChangesExcept("tags_all", "tags") {
input := &backup.UpdateReportPlanInput{
IdempotencyToken: aws.String(resource.UniqueId()),
ReportDeliveryChannel: expandReportDeliveryChannel(d.Get("report_delivery_channel").([]interface{})),
Expand All @@ -219,15 +214,21 @@ func resourceReportPlanUpdate(d *schema.ResourceData, meta interface{}) error {
ReportSetting: expandReportSetting(d.Get("report_setting").([]interface{})),
}

log.Printf("[DEBUG] Updating Backup Report Plan: %#v", input)
log.Printf("[DEBUG] Updating Backup Report Plan: %s", input)
_, err := conn.UpdateReportPlan(input)

if err != nil {
return fmt.Errorf("error updating Backup Report Plan (%s): %w", d.Id(), err)
}

if _, err := waitReportPlanUpdated(conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil {
return fmt.Errorf("error waiting for Backup Report Plan (%s) update: %w", d.Id(), err)
}
}

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

if err := UpdateTags(conn, d.Get("arn").(string), o, n); err != nil {
return fmt.Errorf("error updating tags for Backup Report Plan (%s): %w", d.Id(), err)
}
Expand All @@ -239,13 +240,17 @@ func resourceReportPlanUpdate(d *schema.ResourceData, meta interface{}) error {
func resourceReportPlanDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).BackupConn

input := &backup.DeleteReportPlanInput{
log.Printf("[DEBUG] Deleting Backup Report Plan: %s", d.Id())
_, err := conn.DeleteReportPlan(&backup.DeleteReportPlanInput{
ReportPlanName: aws.String(d.Id()),
}
})

_, err := conn.DeleteReportPlan(input)
if err != nil {
return fmt.Errorf("error deleting Backup Report Plan: %s", err)
return fmt.Errorf("error deleting Backup Report Plan (%s): %w", d.Id(), err)
}

if _, err := waitReportPlanDeleted(conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil {
return fmt.Errorf("error waiting for Backup Report Plan (%s) delete: %w", d.Id(), err)
}

return nil
Expand Down Expand Up @@ -361,3 +366,70 @@ func FindReportPlanByName(conn *backup.Backup, name string) (*backup.ReportPlan,

return output.ReportPlan, nil
}

func statusReportPlanDeployment(conn *backup.Backup, name string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
output, err := FindReportPlanByName(conn, name)

if tfresource.NotFound(err) {
return nil, "", nil
}

if err != nil {
return nil, "", err
}

return output, aws.StringValue(output.DeploymentStatus), nil
}
}

func waitReportPlanCreated(conn *backup.Backup, name string, timeout time.Duration) (*backup.ReportPlan, error) {
stateConf := &resource.StateChangeConf{
Pending: []string{reportPlanDeploymentStatusCreateInProgress},
Target: []string{reportPlanDeploymentStatusCompleted},
Timeout: timeout,
Refresh: statusReportPlanDeployment(conn, name),
}

outputRaw, err := stateConf.WaitForState()

if output, ok := outputRaw.(*backup.ReportPlan); ok {
return output, err
}

return nil, err
}

func waitReportPlanDeleted(conn *backup.Backup, name string, timeout time.Duration) (*backup.ReportPlan, error) {
stateConf := &resource.StateChangeConf{
Pending: []string{reportPlanDeploymentStatusDeleteInProgress},
Target: []string{},
Timeout: timeout,
Refresh: statusReportPlanDeployment(conn, name),
}

outputRaw, err := stateConf.WaitForState()

if output, ok := outputRaw.(*backup.ReportPlan); ok {
return output, err
}

return nil, err
}

func waitReportPlanUpdated(conn *backup.Backup, name string, timeout time.Duration) (*backup.ReportPlan, error) {
stateConf := &resource.StateChangeConf{
Pending: []string{reportPlanDeploymentStatusUpdateInProgress},
Target: []string{reportPlanDeploymentStatusCompleted},
Timeout: timeout,
Refresh: statusReportPlanDeployment(conn, name),
}

outputRaw, err := stateConf.WaitForState()

if output, ok := outputRaw.(*backup.ReportPlan); ok {
return output, err
}

return nil, err
}