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: add cloudwatch log group index policy #40594

Merged
merged 43 commits into from
Dec 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
1fb75ee
Add index policy resource
jcoelho93 Dec 16, 2024
c40cc6a
Regiter resource to the provider
jcoelho93 Dec 16, 2024
bdfed75
Set up tests
jcoelho93 Dec 17, 2024
f4b183f
Generate docs
jcoelho93 Dec 17, 2024
b9ff589
Fix policy document parsing
jcoelho93 Dec 17, 2024
5728203
Fix import and docs
jcoelho93 Dec 17, 2024
64634f7
Improve acceptance tests
jcoelho93 Dec 18, 2024
7a633a0
Refactor to use plugin framework
jcoelho93 Dec 18, 2024
340a5e5
Fix log group name and remove timeouts
jcoelho93 Dec 18, 2024
b284c64
Remove tips
jcoelho93 Dec 18, 2024
d22be84
Fix basic config
jcoelho93 Dec 19, 2024
fc2477b
Format terraform
jcoelho93 Dec 19, 2024
ddd4140
Delete files
jcoelho93 Dec 19, 2024
3994627
Make function private to package
jcoelho93 Dec 19, 2024
ec109b6
Remove extra white line
jcoelho93 Dec 19, 2024
a57b817
Validate policy_document is valid JSON
jcoelho93 Dec 19, 2024
4f267d3
Remove ID attribute
jcoelho93 Dec 19, 2024
b9a14d1
Fix functiona name casing
jcoelho93 Dec 19, 2024
d2d5e19
Fix acceptance tests
jcoelho93 Dec 19, 2024
311383e
Fix semgrep findings
jcoelho93 Dec 19, 2024
7075c2d
Add resource documentation
jcoelho93 Dec 19, 2024
713a8c4
Merge branch 'main' into HEAD
ewbankkit Dec 19, 2024
707ff74
r/aws_cloudwatch_log_index_policy: Tidy up.
ewbankkit Dec 19, 2024
7804b83
Add 'TestAccLogsIndexPolicy_update'.
ewbankkit Dec 19, 2024
810c631
Corrections.
ewbankkit Dec 19, 2024
d11a07e
Acceptance test output:
ewbankkit Dec 19, 2024
ef8ae8c
logs: Generate paginators.
ewbankkit Dec 19, 2024
e6d64e8
Tweak 'findIndexPolicyByLogGroupName'.
ewbankkit Dec 19, 2024
94b9b52
r/aws_cloudwatch_log_account_policy: Tidy up.
ewbankkit Dec 19, 2024
f667dcb
r/aws_cloudwatch_log_data_protection_policy: Tidy up.
ewbankkit Dec 19, 2024
c491d4e
'TrimLogGroupARNWildcardSuffix' -> 'trimLogGroupARNWildcardSuffix'.
ewbankkit Dec 19, 2024
b7c3576
r/aws_cloudwatch_log_destination_policy: Tidy up.
ewbankkit Dec 19, 2024
3f0098d
Add and use 'sdkv2.JSONDocumentSchemaRequired' and 'sdkv2.IAMPolicyDo…
ewbankkit Dec 20, 2024
79d74c2
r/aws_cloudwatch_log_destination: Tidy up.
ewbankkit Dec 20, 2024
ac43537
r/aws_cloudwatch_log_group: Tidy up.
ewbankkit Dec 20, 2024
2000045
d/aws_cloudwatch_log_groups: Tidy up.
ewbankkit Dec 20, 2024
64a7734
r/aws_cloudwatch_log_metric_filter: Tidy up.
ewbankkit Dec 20, 2024
75704f9
r/aws_cloudwatch_query_definition: Tidy up.
ewbankkit Dec 20, 2024
7fd411f
r/aws_cloudwatch_log_resource_policy: Tidy up.
ewbankkit Dec 20, 2024
9699b33
r/aws_cloudwatch_log_stream: Tidy up.
ewbankkit Dec 20, 2024
2669b40
r/aws_cloudwatch_log_subscription_filter: Tidy up.
ewbankkit Dec 20, 2024
e0d5b2d
r/aws_cloudwatch_log_anomaly_detector: Tidy up.
ewbankkit Dec 20, 2024
dbed232
Fix semgrep 'ci.logs-in-var-name' and 'ci.logs-in-func-name'.
ewbankkit Dec 20, 2024
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/40594.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
aws_cloudwatch_log_index_policy
```
22 changes: 22 additions & 0 deletions internal/sdkv2/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
package sdkv2

import (
"sync"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)

// Adapted from https://github.com/hashicorp/terraform-provider-google/google/datasource_helpers.go. Thanks!
Expand Down Expand Up @@ -61,3 +64,22 @@ func DataSourceSchemaFromResourceSchema(rs map[string]*schema.Schema) map[string

return ds
}

// JSONDocumentSchemaRequired returns the standard schema for a required JSON document.
var JSONDocumentSchemaRequired = sync.OnceValue(jsonDocumentSchemaRequiredFunc(SuppressEquivalentJSONDocuments))

// IAMPolicyDocumentSchemaRequired returns the standard schema for a required IAM policy JSON document.
var IAMPolicyDocumentSchemaRequired = sync.OnceValue(jsonDocumentSchemaRequiredFunc(SuppressEquivalentIAMPolicyDocuments))

func jsonDocumentSchemaRequiredFunc(diffSuppressFunc schema.SchemaDiffSuppressFunc) func() *schema.Schema {
return func() *schema.Schema {
return &schema.Schema{
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringIsJSON,
DiffSuppressFunc: diffSuppressFunc,
DiffSuppressOnRefresh: true,
StateFunc: NormalizeJsonStringSchemaStateFunc,
}
}
}
28 changes: 28 additions & 0 deletions internal/sdkv2/suppress.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,39 @@ package sdkv2
import (
"strings"

awspolicy "github.com/hashicorp/awspolicyequivalence"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-provider-aws/internal/json"
)

// SuppressEquivalentStringCaseInsensitive provides custom difference suppression
// for strings that are equal under case-insensitivity.
func SuppressEquivalentStringCaseInsensitive(k, old, new string, d *schema.ResourceData) bool {
return strings.EqualFold(old, new)
}

// SuppressEquivalentJSONDocuments provides custom difference suppression
// for JSON documents in the given strings that are equivalent.
func SuppressEquivalentJSONDocuments(k, old, new string, d *schema.ResourceData) bool {
return json.EqualStrings(old, new)
}

// SuppressEquivalentIAMPolicyDocuments provides custom difference suppression
// for IAM policy documents in the given strings that are equivalent.
func SuppressEquivalentIAMPolicyDocuments(k, old, new string, d *schema.ResourceData) bool {
if equalEmptyJSONStrings(old, new) {
return true
}

equivalent, err := awspolicy.PoliciesAreEquivalent(old, new)
if err != nil {
return false
}

return equivalent
}

func equalEmptyJSONStrings(old, new string) bool {
old, new = strings.TrimSpace(old), strings.TrimSpace(new)
return (old == "" || old == "{}") && (new == "" || new == "{}")
}
80 changes: 50 additions & 30 deletions internal/service/logs/account_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs"
"github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types"
awstypes "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
Expand All @@ -20,6 +20,7 @@ import (
"github.com/hashicorp/terraform-provider-aws/internal/enum"
"github.com/hashicorp/terraform-provider-aws/internal/errs"
"github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag"
"github.com/hashicorp/terraform-provider-aws/internal/sdkv2"
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
"github.com/hashicorp/terraform-provider-aws/internal/verify"
"github.com/hashicorp/terraform-provider-aws/names"
Expand All @@ -38,17 +39,7 @@ func resourceAccountPolicy() *schema.Resource {
},

Schema: map[string]*schema.Schema{
"policy_document": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validAccountPolicyDocument,
DiffSuppressFunc: verify.SuppressEquivalentJSONDiffs,
DiffSuppressOnRefresh: true,
StateFunc: func(v interface{}) string {
json, _ := structure.NormalizeJsonString(v)
return json
},
},
"policy_document": sdkv2.JSONDocumentSchemaRequired(),
"policy_name": {
Type: schema.TypeString,
Required: true,
Expand All @@ -58,13 +49,13 @@ func resourceAccountPolicy() *schema.Resource {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateDiagFunc: enum.Validate[types.PolicyType](),
ValidateDiagFunc: enum.Validate[awstypes.PolicyType](),
},
names.AttrScope: {
Type: schema.TypeString,
Optional: true,
Default: types.ScopeAll,
ValidateDiagFunc: enum.Validate[types.Scope](),
Default: awstypes.ScopeAll,
ValidateDiagFunc: enum.Validate[awstypes.Scope](),
},
"selection_criteria": {
Type: schema.TypeString,
Expand All @@ -88,8 +79,8 @@ func resourceAccountPolicyPut(ctx context.Context, d *schema.ResourceData, meta
input := &cloudwatchlogs.PutAccountPolicyInput{
PolicyDocument: aws.String(policy),
PolicyName: aws.String(name),
PolicyType: types.PolicyType(d.Get("policy_type").(string)),
Scope: types.Scope(d.Get(names.AttrScope).(string)),
PolicyType: awstypes.PolicyType(d.Get("policy_type").(string)),
Scope: awstypes.Scope(d.Get(names.AttrScope).(string)),
}

if v, ok := d.GetOk("selection_criteria"); ok {
Expand All @@ -99,7 +90,7 @@ func resourceAccountPolicyPut(ctx context.Context, d *schema.ResourceData, meta
output, err := conn.PutAccountPolicy(ctx, input)

if err != nil {
return sdkdiag.AppendErrorf(diags, "creating CloudWatch Logs Account Policy (%s): %s", name, err)
return sdkdiag.AppendErrorf(diags, "putting CloudWatch Logs Account Policy (%s): %s", name, err)
}

d.SetId(aws.ToString(output.AccountPolicy.PolicyName))
Expand All @@ -111,7 +102,7 @@ func resourceAccountPolicyRead(ctx context.Context, d *schema.ResourceData, meta
var diags diag.Diagnostics
conn := meta.(*conns.AWSClient).LogsClient(ctx)

output, err := findAccountPolicyByTwoPartKey(ctx, conn, types.PolicyType(d.Get("policy_type").(string)), d.Id())
output, err := findAccountPolicyByTwoPartKey(ctx, conn, awstypes.PolicyType(d.Get("policy_type").(string)), d.Id())

if !d.IsNewResource() && tfresource.NotFound(err) {
log.Printf("[WARN] CloudWatch Logs Account Policy (%s) not found, removing from state", d.Id())
Expand Down Expand Up @@ -149,10 +140,10 @@ func resourceAccountPolicyDelete(ctx context.Context, d *schema.ResourceData, me
log.Printf("[DEBUG] Deleting CloudWatch Logs Account Policy: %s", d.Id())
_, err := conn.DeleteAccountPolicy(ctx, &cloudwatchlogs.DeleteAccountPolicyInput{
PolicyName: aws.String(d.Id()),
PolicyType: types.PolicyType(d.Get("policy_type").(string)),
PolicyType: awstypes.PolicyType(d.Get("policy_type").(string)),
})

if errs.IsA[*types.ResourceNotFoundException](err) {
if errs.IsA[*awstypes.ResourceNotFoundException](err) {
return diags
}

Expand All @@ -178,15 +169,48 @@ func resourceAccountPolicyImport(d *schema.ResourceData, meta interface{}) ([]*s
return []*schema.ResourceData{d}, nil
}

func findAccountPolicyByTwoPartKey(ctx context.Context, conn *cloudwatchlogs.Client, policyType types.PolicyType, policyName string) (*types.AccountPolicy, error) {
input := &cloudwatchlogs.DescribeAccountPoliciesInput{
func findAccountPolicyByTwoPartKey(ctx context.Context, conn *cloudwatchlogs.Client, policyType awstypes.PolicyType, policyName string) (*awstypes.AccountPolicy, error) {
input := cloudwatchlogs.DescribeAccountPoliciesInput{
PolicyName: aws.String(policyName),
PolicyType: policyType,
}
output, err := findAccountPolicy(ctx, conn, &input)

output, err := conn.DescribeAccountPolicies(ctx, input)
if err != nil {
return nil, err
}

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

if errs.IsA[*types.ResourceNotFoundException](err) {
return output, err
}

func findAccountPolicy(ctx context.Context, conn *cloudwatchlogs.Client, input *cloudwatchlogs.DescribeAccountPoliciesInput) (*awstypes.AccountPolicy, error) {
output, err := findAccountPolicies(ctx, conn, input)

if err != nil {
return nil, err
}

return tfresource.AssertSingleValueResult(output)
}

func findAccountPolicies(ctx context.Context, conn *cloudwatchlogs.Client, input *cloudwatchlogs.DescribeAccountPoliciesInput) ([]awstypes.AccountPolicy, error) {
var output []awstypes.AccountPolicy

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

output = append(output, page.AccountPolicies...)

return !lastPage
})

if errs.IsA[*awstypes.ResourceNotFoundException](err) {
return nil, &retry.NotFoundError{
LastError: err,
LastRequest: input,
Expand All @@ -197,9 +221,5 @@ func findAccountPolicyByTwoPartKey(ctx context.Context, conn *cloudwatchlogs.Cli
return nil, err
}

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

return tfresource.AssertSingleValueResult(output.AccountPolicies)
return output, nil
}
32 changes: 16 additions & 16 deletions internal/service/logs/account_policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,21 +185,6 @@ func testAccCheckAccountPolicyExists(ctx context.Context, n string, v *types.Acc
}
}

func testAccAccountPolicyImportStateIDFunc(resourceName string) resource.ImportStateIdFunc {
return func(s *terraform.State) (string, error) {
rs, ok := s.RootModule().Resources[resourceName]
if !ok {
return "", fmt.Errorf("Not found: %s", resourceName)
}

policyName := rs.Primary.ID
policyType := rs.Primary.Attributes["policy_type"]
stateID := fmt.Sprintf("%s:%s", policyName, policyType)

return stateID, nil
}
}

func testAccCheckAccountPolicyDestroy(ctx context.Context) resource.TestCheckFunc {
return func(s *terraform.State) error {
conn := acctest.Provider.Meta().(*conns.AWSClient).LogsClient(ctx)
Expand All @@ -219,13 +204,28 @@ func testAccCheckAccountPolicyDestroy(ctx context.Context) resource.TestCheckFun
return err
}

return fmt.Errorf("CloudWatch Logs Resource Policy still exists: %s", rs.Primary.ID)
return fmt.Errorf("CloudWatch Logs Account Policy still exists: %s", rs.Primary.ID)
}

return nil
}
}

func testAccAccountPolicyImportStateIDFunc(n string) resource.ImportStateIdFunc {
return func(s *terraform.State) (string, error) {
rs, ok := s.RootModule().Resources[n]
if !ok {
return "", fmt.Errorf("Not found: %s", n)
}

policyName := rs.Primary.ID
policyType := rs.Primary.Attributes["policy_type"]
stateID := fmt.Sprintf("%s:%s", policyName, policyType)

return stateID, nil
}
}

func testAccCheckAccountHasSubscriptionFilterPolicy(ctx context.Context, resourceName string, rName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
expectedJSONTemplate := `{
Expand Down
Loading
Loading