-
Notifications
You must be signed in to change notification settings - Fork 392
[datadog_compliance_resource_evaluation_filter] Adding resource evaluation filters as a terraform resource #3004
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
Open
Matzoc
wants to merge
22
commits into
master
Choose a base branch
from
manuel.costa/resource-filter-resource
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
1056b0e
Initial resource evaluation filter implementation
Matzoc b07e39f
revert CloudProvider string refactor
Matzoc ac8063f
updated terraform provider to use skipCache
Matzoc 353a825
Adding descriptions to schema. Removing debug specific code
Matzoc ae91d24
adding documentation examples
Matzoc 5c66b29
Merge branch 'master' into manuel.costa/resource-filter-resource
Matzoc bac3ce1
updating go client version
Matzoc a0bd6f9
pushing the docs changes
Matzoc 8adc196
go fmt
Matzoc a06ec10
adding generated cassettes
Matzoc 9f1767e
swapping from set to list to stay consistent with backend implementat…
Matzoc 25460bf
applying suggested documentation changes.
Matzoc 9e97616
adding missing ponctuation
Matzoc f121dc2
pushing missing doc
Matzoc 8913363
renaming resource
Matzoc 8123dc6
removed debug line. Addressed various comments.
Matzoc d7bade0
updated error messages
Matzoc 595ea54
fix resources not being deleted when id or cloud provider is changed
Matzoc fbde816
remove commented out code
Matzoc 5779d0d
adding updated cassettes
Matzoc 3ab35b2
Updating remaining instances of resource evaluation filters to compli…
Matzoc 838d17e
fixing incorrect state management (overlap between id field and id in…
Matzoc File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
309 changes: 309 additions & 0 deletions
309
datadog/fwprovider/resource_datadog_compliance_resource_evaluation_filter.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,309 @@ | ||
package fwprovider | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"regexp" | ||
"strings" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" | ||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" | ||
"github.com/hashicorp/terraform-plugin-framework/attr" | ||
"github.com/hashicorp/terraform-plugin-framework/diag" | ||
"github.com/hashicorp/terraform-plugin-framework/path" | ||
"github.com/hashicorp/terraform-plugin-framework/resource" | ||
"github.com/hashicorp/terraform-plugin-framework/resource/schema" | ||
"github.com/hashicorp/terraform-plugin-framework/schema/validator" | ||
"github.com/hashicorp/terraform-plugin-framework/types" | ||
|
||
"github.com/DataDog/datadog-api-client-go/v2/api/datadogV2" | ||
|
||
"github.com/terraform-providers/terraform-provider-datadog/datadog/internal/utils" | ||
) | ||
|
||
type ComplianceResourceEvaluationFilter struct { | ||
API *datadogV2.SecurityMonitoringApi | ||
Auth context.Context | ||
} | ||
|
||
type ResourceEvaluationFilterModel struct { | ||
CloudProvider types.String `tfsdk:"cloud_provider"` | ||
ResourceID types.String `tfsdk:"resource_id"` | ||
Tags types.List `tfsdk:"tags"` | ||
|
||
ID types.String `tfsdk:"id"` | ||
} | ||
|
||
func NewComplianceResourceEvaluationFilter() resource.Resource { | ||
return &ComplianceResourceEvaluationFilter{} | ||
} | ||
|
||
var ( | ||
_ resource.ResourceWithConfigure = &ComplianceResourceEvaluationFilter{} | ||
_ resource.ResourceWithImportState = &ComplianceResourceEvaluationFilter{} | ||
) | ||
|
||
func (r *ComplianceResourceEvaluationFilter) Configure(_ context.Context, request resource.ConfigureRequest, response *resource.ConfigureResponse) { | ||
providerData, _ := request.ProviderData.(*FrameworkProvider) | ||
r.API = providerData.DatadogApiInstances.GetSecurityMonitoringApiV2() | ||
r.Auth = providerData.Auth | ||
} | ||
|
||
func (r *ComplianceResourceEvaluationFilter) Metadata(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { | ||
resp.TypeName = "compliance_resource_evaluation_filter" | ||
} | ||
|
||
var tagFormatValidator = stringvalidator.RegexMatches( | ||
regexp.MustCompile(`^[^:]+:[^:]+$`), | ||
"each tag must be in the format 'key:value' (colon-separated)", | ||
) | ||
|
||
func toSliceString(list types.List) ([]string, diag.Diagnostics) { | ||
var diags diag.Diagnostics | ||
result := make([]string, 0) | ||
|
||
if list.IsNull() || list.IsUnknown() { | ||
return result, nil | ||
} | ||
|
||
for _, elem := range list.Elements() { | ||
strVal, ok := elem.(types.String) | ||
if !ok { | ||
diags.AddError("Invalid element type creating tags list", fmt.Sprintf("Expected string in list but found %T", elem)) | ||
continue | ||
} | ||
result = append(result, strVal.ValueString()) | ||
} | ||
|
||
return result, diags | ||
} | ||
|
||
func (r *ComplianceResourceEvaluationFilter) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { | ||
resp.Schema = schema.Schema{ | ||
Description: "Provides a Datadog ComplianceResourceEvaluationFilter resource. This can be used to create and manage a compliance resource evaluation filter.", | ||
Attributes: map[string]schema.Attribute{ | ||
"cloud_provider": schema.StringAttribute{ | ||
Required: true, | ||
Description: "The cloud provider of the filter's targeted resource. Only `aws`, `gcp` or `azure` are considered valid cloud providers.", | ||
PlanModifiers: []planmodifier.String{ | ||
stringplanmodifier.RequiresReplace(), | ||
}, | ||
}, | ||
"resource_id": schema.StringAttribute{ | ||
Required: true, | ||
Description: "The ID of the of the filter's targeted resource. Different cloud providers target different resource IDs:\n - `aws`: account id \n - `gcp`: project id\n - `azure`: subscription id", | ||
PlanModifiers: []planmodifier.String{ | ||
stringplanmodifier.RequiresReplace(), | ||
}}, | ||
|
||
"id": schema.StringAttribute{ | ||
Description: "The ID of the resource evaluation filter resource.", | ||
Computed: true, | ||
}, | ||
"tags": schema.ListAttribute{ | ||
Required: true, | ||
ElementType: types.StringType, | ||
Validators: []validator.List{ | ||
listvalidator.ValueStringsAre(tagFormatValidator), | ||
}, | ||
Description: "List of tags to filter misconfiguration detections. Each entry should follow the format: \"key\":\"value\".", | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func (r *ComplianceResourceEvaluationFilter) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { | ||
var state ResourceEvaluationFilterModel | ||
response.Diagnostics.Append(request.Plan.Get(ctx, &state)...) | ||
if response.Diagnostics.HasError() { | ||
return | ||
} | ||
|
||
body, diags := r.buildUpdateResourceEvaluationFilterRequest(ctx, &state) | ||
response.Diagnostics.Append(diags...) | ||
if response.Diagnostics.HasError() { | ||
return | ||
} | ||
|
||
resp, _, err := r.API.UpdateResourceEvaluationFilters(r.Auth, *body) | ||
if err != nil { | ||
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error updating compliance resource evaluation filter")) | ||
return | ||
} | ||
if err := utils.CheckForUnparsed(resp); err != nil { | ||
response.Diagnostics.AddError("response contains unparsedObject", err.Error()) | ||
return | ||
} | ||
|
||
attributes := resp.Data.GetAttributes() | ||
r.UpdateState(ctx, &state, &attributes) | ||
response.Diagnostics.Append(response.State.Set(ctx, &state)...) | ||
} | ||
|
||
func convertStringSliceToAttrValues(s []string) []attr.Value { | ||
out := make([]attr.Value, len(s)) | ||
for i, v := range s { | ||
out[i] = types.StringValue(v) | ||
} | ||
return out | ||
} | ||
|
||
func (r *ComplianceResourceEvaluationFilter) UpdateState(_ context.Context, state *ResourceEvaluationFilterModel, attributes *datadogV2.ResourceFilterAttributes) { | ||
for p, accounts := range attributes.CloudProvider { | ||
for resource_id, tagList := range accounts { | ||
tags := types.ListValueMust(types.StringType, convertStringSliceToAttrValues(tagList)) | ||
state.CloudProvider = types.StringValue(p) | ||
state.ID = types.StringValue(p + ":" + resource_id) | ||
state.ResourceID = types.StringValue(resource_id) | ||
state.Tags = tags | ||
break | ||
} | ||
break | ||
} | ||
} | ||
|
||
func (r *ComplianceResourceEvaluationFilter) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { | ||
var state ResourceEvaluationFilterModel | ||
response.Diagnostics.Append(request.State.Get(ctx, &state)...) | ||
if response.Diagnostics.HasError() { | ||
return | ||
} | ||
|
||
if state.CloudProvider.IsNull() || state.CloudProvider.IsUnknown() { | ||
response.Diagnostics.AddError("Missing cloud_provider", "cloud_provider is required for lookup") | ||
return | ||
} | ||
|
||
provider := state.CloudProvider.ValueString() | ||
skipCache := true | ||
|
||
params := datadogV2.GetResourceEvaluationFiltersOptionalParameters{ | ||
CloudProvider: &provider, | ||
AccountId: state.ID.ValueStringPointer(), | ||
SkipCache: &skipCache, | ||
} | ||
resp, _, err := r.API.GetResourceEvaluationFilters(r.Auth, params) | ||
if err != nil { | ||
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error retrieving ComplianceResourceEvaluationFilter")) | ||
return | ||
} | ||
|
||
attributes := resp.Data.GetAttributes() | ||
r.UpdateState(ctx, &state, &attributes) | ||
response.Diagnostics.Append(response.State.Set(ctx, &state)...) | ||
} | ||
|
||
func (r *ComplianceResourceEvaluationFilter) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { | ||
var state ResourceEvaluationFilterModel | ||
response.Diagnostics.Append(request.Plan.Get(ctx, &state)...) | ||
if response.Diagnostics.HasError() { | ||
return | ||
} | ||
|
||
body, diags := r.buildUpdateResourceEvaluationFilterRequest(ctx, &state) | ||
response.Diagnostics.Append(diags...) | ||
if response.Diagnostics.HasError() { | ||
return | ||
} | ||
|
||
resp, _, err := r.API.UpdateResourceEvaluationFilters(r.Auth, *body) | ||
if err != nil { | ||
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error updating ComplianceResourceEvaluationFilter")) | ||
return | ||
} | ||
if err := utils.CheckForUnparsed(resp); err != nil { | ||
response.Diagnostics.AddError("response contains unparsedObject", err.Error()) | ||
return | ||
} | ||
|
||
attributes := resp.Data.GetAttributes() | ||
r.UpdateState(ctx, &state, &attributes) | ||
response.Diagnostics.Append(response.State.Set(ctx, &state)...) | ||
} | ||
|
||
func (r *ComplianceResourceEvaluationFilter) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { | ||
var state ResourceEvaluationFilterModel | ||
response.Diagnostics.Append(request.State.Get(ctx, &state)...) | ||
if response.Diagnostics.HasError() { | ||
return | ||
} | ||
|
||
state.Tags = types.ListValueMust(types.StringType, []attr.Value{}) | ||
body, diags := r.buildUpdateResourceEvaluationFilterRequest(ctx, &state) | ||
response.Diagnostics.Append(diags...) | ||
if response.Diagnostics.HasError() { | ||
return | ||
} | ||
|
||
resp, _, err := r.API.UpdateResourceEvaluationFilters(r.Auth, *body) | ||
if err != nil { | ||
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error deleting ComplianceResourceEvaluationFilter")) | ||
return | ||
} | ||
if err := utils.CheckForUnparsed(resp); err != nil { | ||
response.Diagnostics.AddError("response contains unparsedObject", err.Error()) | ||
return | ||
} | ||
} | ||
|
||
func (r *ComplianceResourceEvaluationFilter) ImportState( | ||
ctx context.Context, | ||
req resource.ImportStateRequest, | ||
resp *resource.ImportStateResponse, | ||
) { | ||
parts := strings.Split(req.ID, ":") | ||
if len(parts) != 2 { | ||
resp.Diagnostics.AddError( | ||
"Invalid import format", | ||
`Expected format: "cloud_provider:id" (e.g., "aws:123456789")`, | ||
) | ||
return | ||
} | ||
|
||
cloudProvider := parts[0] | ||
id := parts[1] | ||
|
||
resp.State.SetAttribute(ctx, path.Root("cloud_provider"), cloudProvider) | ||
resp.State.SetAttribute(ctx, path.Root("id"), id) | ||
} | ||
|
||
func (r *ComplianceResourceEvaluationFilter) buildUpdateResourceEvaluationFilterRequest(ctx context.Context, state *ResourceEvaluationFilterModel) (*datadogV2.UpdateResourceEvaluationFiltersRequest, diag.Diagnostics) { | ||
diags := diag.Diagnostics{} | ||
data := datadogV2.NewUpdateResourceEvaluationFiltersRequestDataWithDefaults() | ||
|
||
tagsList, tagDiags := toSliceString(state.Tags) | ||
diags.Append(tagDiags...) | ||
if tagDiags.HasError() { | ||
return nil, diags | ||
} | ||
|
||
if state.CloudProvider.IsNull() || state.CloudProvider.IsUnknown() { | ||
diags.AddError("Missing cloud_provider", "cloud_provider is required but was null or unknown") | ||
return nil, diags | ||
} | ||
if state.ResourceID.IsNull() || state.ResourceID.IsUnknown() { | ||
diags.AddError("Missing id", "id is required but was null or unknown") | ||
return nil, diags | ||
} | ||
|
||
attributes := datadogV2.ResourceFilterAttributes{ | ||
CloudProvider: map[string]map[string][]string{ | ||
state.CloudProvider.ValueString(): { | ||
state.ResourceID.ValueString(): tagsList, | ||
}, | ||
}, | ||
} | ||
|
||
data.SetId(string(datadogV2.RESOURCEFILTERREQUESTTYPE_CSM_RESOURCE_FILTER)) | ||
data.SetType(datadogV2.RESOURCEFILTERREQUESTTYPE_CSM_RESOURCE_FILTER) | ||
data.SetAttributes(attributes) | ||
|
||
req := datadogV2.NewUpdateResourceEvaluationFiltersRequestWithDefaults() | ||
req.SetData(*data) | ||
|
||
return req, diags | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
2025-05-23T17:45:25.774964+01:00 |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.