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

F config recording strategy #32066

Merged
merged 10 commits into from
Jun 21, 2023
3 changes: 3 additions & 0 deletions .changelog/32007.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_config_configuration_recorder: Add `exclusion_by_resource_types` and `recording_strategy` attributes to the `recording_group` configuration block
```
7 changes: 4 additions & 3 deletions internal/service/configservice/configservice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ func TestAccConfigService_serial(t *testing.T) {
"importBasic": testAccConfigurationRecorderStatus_importBasic,
},
"ConfigurationRecorder": {
"basic": testAccConfigurationRecorder_basic,
"allParams": testAccConfigurationRecorder_allParams,
"importBasic": testAccConfigurationRecorder_importBasic,
"basic": testAccConfigurationRecorder_basic,
"allParams": testAccConfigurationRecorder_allParams,
"recordStrategy": testAccConfigurationRecorder_recordStrategy,
"disappears": testAccConfigurationRecorder_disappears,
},
"ConformancePack": {
"basic": testAccConformancePack_basic,
Expand Down
207 changes: 154 additions & 53 deletions internal/service/configservice/configuration_recorder.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@ package configservice
import (
"context"
"errors"
"log"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/configservice"
"github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"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"
"github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag"
"github.com/hashicorp/terraform-provider-aws/internal/flex"
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
"github.com/hashicorp/terraform-provider-aws/internal/verify"
"github.com/hashicorp/terraform-provider-aws/names"
)

// @SDKResource("aws_config_configuration_recorder")
Expand All @@ -29,6 +32,10 @@ func ResourceConfigurationRecorder() *schema.Resource {
StateContext: schema.ImportStatePassthroughContext,
},

CustomizeDiff: customdiff.All(
resourceConfigCustomizeDiff,
),

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Expand All @@ -37,11 +44,6 @@ func ResourceConfigurationRecorder() *schema.Resource {
Default: "default",
ValidateFunc: validation.StringLenBetween(0, 256),
},
"role_arn": {
Type: schema.TypeString,
Required: true,
ValidateFunc: verify.ValidARN,
},
"recording_group": {
Type: schema.TypeList,
Optional: true,
Expand All @@ -54,19 +56,51 @@ func ResourceConfigurationRecorder() *schema.Resource {
Optional: true,
Default: true,
},
"exclusion_by_resource_types": {
Type: schema.TypeList,
Optional: true,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"resource_types": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
},
},
"include_global_resource_types": {
Type: schema.TypeBool,
Optional: true,
},
"recording_strategy": {
Type: schema.TypeList,
Optional: true,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"use_only": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice(configservice.RecordingStrategyType_Values(), false),
},
},
},
},
"resource_types": {
Type: schema.TypeSet,
Set: schema.HashString,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
},
},
"role_arn": {
Type: schema.TypeString,
Required: true,
ValidateFunc: verify.ValidARN,
},
},
}
}
Expand All @@ -76,24 +110,26 @@ func resourceConfigurationRecorderPut(ctx context.Context, d *schema.ResourceDat
conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx)

name := d.Get("name").(string)
recorder := configservice.ConfigurationRecorder{
Name: aws.String(name),
RoleARN: aws.String(d.Get("role_arn").(string)),
input := &configservice.PutConfigurationRecorderInput{
ConfigurationRecorder: &configservice.ConfigurationRecorder{
Name: aws.String(name),
RoleARN: aws.String(d.Get("role_arn").(string)),
},
}

if g, ok := d.GetOk("recording_group"); ok {
recorder.RecordingGroup = expandRecordingGroup(g.([]interface{}))
if v, ok := d.GetOk("recording_group"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil {
input.ConfigurationRecorder.RecordingGroup = expandRecordingGroup(v.([]interface{})[0].(map[string]interface{}))
}

input := configservice.PutConfigurationRecorderInput{
ConfigurationRecorder: &recorder,
}
_, err := conn.PutConfigurationRecorderWithContext(ctx, &input)
_, err := conn.PutConfigurationRecorderWithContext(ctx, input)

if err != nil {
return sdkdiag.AppendErrorf(diags, "Creating Configuration Recorder failed: %s", err)
return sdkdiag.AppendErrorf(diags, "putting ConfigService Configuration Recorder (%s): %s", name, err)
}

d.SetId(name)
if d.IsNewResource() {
d.SetId(name)
}

return append(diags, resourceConfigurationRecorderRead(ctx, d, meta)...)
}
Expand All @@ -102,46 +138,24 @@ func resourceConfigurationRecorderRead(ctx context.Context, d *schema.ResourceDa
var diags diag.Diagnostics
conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx)

input := configservice.DescribeConfigurationRecordersInput{
ConfigurationRecorderNames: []*string{aws.String(d.Id())},
}
out, err := conn.DescribeConfigurationRecordersWithContext(ctx, &input)
if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, configservice.ErrCodeNoSuchConfigurationRecorderException) {
create.LogNotFoundRemoveState(names.ConfigService, create.ErrActionReading, ResNameConfigurationRecorder, d.Id())
d.SetId("")
return diags
}

if err != nil {
return create.DiagError(names.ConfigService, create.ErrActionReading, ResNameConfigurationRecorder, d.Id(), err)
}
recorder, err := FindConfigurationRecorderByName(ctx, conn, d.Id())

numberOfRecorders := len(out.ConfigurationRecorders)
if !d.IsNewResource() && numberOfRecorders < 1 {
create.LogNotFoundRemoveState(names.ConfigService, create.ErrActionReading, ResNameConfigurationRecorder, d.Id())
if !d.IsNewResource() && tfresource.NotFound(err) {
log.Printf("[WARN] ConfigService Configuration Recorder (%s) not found, removing from state", d.Id())
d.SetId("")
return diags
}

if d.IsNewResource() && numberOfRecorders < 1 {
return create.DiagError(names.ConfigService, create.ErrActionReading, ResNameConfigurationRecorder, d.Id(), errors.New("none found"))
}

if numberOfRecorders > 1 {
return sdkdiag.AppendErrorf(diags, "Expected exactly 1 Configuration Recorder, received %d: %#v",
numberOfRecorders, out.ConfigurationRecorders)
if err != nil {
return sdkdiag.AppendErrorf(diags, "reading ConfigService Configuration Recorder (%s): %s", d.Id(), err)
}

recorder := out.ConfigurationRecorders[0]

d.Set("name", recorder.Name)
d.Set("role_arn", recorder.RoleARN)

if recorder.RecordingGroup != nil {
flattened := flattenRecordingGroup(recorder.RecordingGroup)
err = d.Set("recording_group", flattened)
if err != nil {
return sdkdiag.AppendErrorf(diags, "Failed to set recording_group: %s", err)
if err := d.Set("recording_group", flattenRecordingGroup(recorder.RecordingGroup)); err != nil {
return sdkdiag.AppendErrorf(diags, "setting recording_group: %s", err)
}
}

Expand All @@ -151,14 +165,101 @@ func resourceConfigurationRecorderRead(ctx context.Context, d *schema.ResourceDa
func resourceConfigurationRecorderDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
var diags diag.Diagnostics
conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx)
input := configservice.DeleteConfigurationRecorderInput{

log.Printf("[DEBUG] Deleting ConfigService Configuration Recorder: %s", d.Id())
_, err := conn.DeleteConfigurationRecorderWithContext(ctx, &configservice.DeleteConfigurationRecorderInput{
ConfigurationRecorderName: aws.String(d.Id()),
})

if tfawserr.ErrCodeEquals(err, configservice.ErrCodeNoSuchConfigurationRecorderException) {
return diags
}
_, err := conn.DeleteConfigurationRecorderWithContext(ctx, &input)

if err != nil {
if !tfawserr.ErrCodeEquals(err, configservice.ErrCodeNoSuchConfigurationRecorderException) {
return sdkdiag.AppendErrorf(diags, "Deleting Configuration Recorder failed: %s", err)
}
return sdkdiag.AppendErrorf(diags, "deleting ConfigService Configuration Recorder (%s): %s", d.Id(), err)
}

return diags
}

func FindConfigurationRecorderByName(ctx context.Context, conn *configservice.ConfigService, name string) (*configservice.ConfigurationRecorder, error) {
input := &configservice.DescribeConfigurationRecordersInput{
ConfigurationRecorderNames: aws.StringSlice([]string{name}),
}

output, err := conn.DescribeConfigurationRecordersWithContext(ctx, input)

if tfawserr.ErrCodeEquals(err, configservice.ErrCodeNoSuchConfigurationRecorderException) {
return nil, &retry.NotFoundError{
LastError: err,
LastRequest: input,
}
}

if err != nil {
return nil, err
}

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

return tfresource.AssertSinglePtrResult(output.ConfigurationRecorders)
}

func resourceConfigCustomizeDiff(_ context.Context, diff *schema.ResourceDiff, v interface{}) error {
if diff.Id() == "" { // New resource.

if g, ok := diff.GetOk("recording_group"); ok {
group := g.([]interface{})[0].(map[string]interface{})

if h, ok := group["all_supported"]; ok {
if i, ok := group["recording_strategy"]; ok && len(i.([]interface{})) > 0 && i.([]interface{})[0] != nil {
strategy := i.([]interface{})[0].(map[string]interface{})

if j, ok := strategy["use_only"].(string); ok {
if h.(bool) && j != configservice.RecordingStrategyTypeAllSupportedResourceTypes {
return errors.New(` Invalid record group strategy , all_supported must be set to true `)
}

if k, ok := group["exclusion_by_resource_types"]; ok && len(k.([]interface{})) > 0 && k.([]interface{})[0] != nil {
if h.(bool) {
return errors.New(` Invalid record group , all_supported must be set to false when exclusion_by_resource_types is set `)
}

if j != configservice.RecordingStrategyTypeExclusionByResourceTypes {
return errors.New(` Invalid record group strategy , use only must be set to EXCLUSION_BY_RESOURCE_TYPES`)
}

if l, ok := group["resource_types"]; ok {
resourceTypes := flex.ExpandStringSet(l.(*schema.Set))
if len(resourceTypes) > 0 {
return errors.New(` Invalid record group , resource_types must not be set when exclusion_by_resource_types is set `)
}
}
}

if l, ok := group["resource_types"]; ok {
resourceTypes := flex.ExpandStringSet(l.(*schema.Set))
if len(resourceTypes) > 0 {
if h.(bool) {
return errors.New(` Invalid record group , all_supported must be set to false when resource_types is set `)
}

if j != configservice.RecordingStrategyTypeInclusionByResourceTypes {
return errors.New(` Invalid record group strategy , use only must be set to INCLUSION_BY_RESOURCE_TYPES`)
}

if m, ok := group["exclusion_by_resource_types"]; ok && len(m.([]interface{})) > 0 && i.([]interface{})[0] != nil {
return errors.New(` Invalid record group , exclusion_by_resource_types must not be set when resource_types is set `)
}
}
}
}
}

}
}
}
return nil
}
Loading