diff --git a/docs/data-sources/content_category_versions.md b/docs/data-sources/content_category_versions.md index 9e2c22eb..e97b2af6 100644 --- a/docs/data-sources/content_category_versions.md +++ b/docs/data-sources/content_category_versions.md @@ -5,7 +5,7 @@ description: |- This data source provides information about available content category versions for pinning in content update policies. API Scopes The following API scopes are required: - Content update policy | Read + Content Update Policy | Read --- # crowdstrike_content_category_versions (Data Source) @@ -16,7 +16,7 @@ This data source provides information about available content category versions The following API scopes are required: -- Content update policy | Read +- Content Update Policy | Read ## Example Usage diff --git a/docs/data-sources/content_update_policies.md b/docs/data-sources/content_update_policies.md new file mode 100644 index 00000000..86d7f3ff --- /dev/null +++ b/docs/data-sources/content_update_policies.md @@ -0,0 +1,134 @@ +--- +page_title: "crowdstrike_content_update_policies Data Source - crowdstrike" +subcategory: "Content Update Policies" +description: |- + This data source provides information about content update policies in Falcon. + API Scopes + The following API scopes are required: + Content Update Policy | Read +--- + +# crowdstrike_content_update_policies (Data Source) + +This data source provides information about content update policies in Falcon. + +## API Scopes + +The following API scopes are required: + +- Content Update Policy | Read + + +## Example Usage + +```terraform +terraform { + required_providers { + crowdstrike = { + source = "registry.terraform.io/crowdstrike/crowdstrike" + } + } +} + +provider "crowdstrike" { + cloud = "us-2" +} + +# Get all content update policies +data "crowdstrike_content_update_policies" "all" {} + +# Get enabled policies +data "crowdstrike_content_update_policies" "enabled" { + enabled = true +} + +# Use FQL filter with sorting +data "crowdstrike_content_update_policies" "filtered_and_sorted" { + filter = "enabled:true+name:'*prod*'" + sort = "name.asc" +} + +# Get policies by ID +data "crowdstrike_content_update_policies" "specific" { + ids = [ + "policy-id-1", + "policy-id-2" + ] +} +``` + + +## Schema + +### Optional + +- `created_by` (String) Filter policies by the user who created them. All provided filter attributes must match for a policy to be returned (omitted attributes are ignored). Supports wildcard matching with '*' where '*' matches any sequence of characters until the end of the string or until the next literal character in the pattern is found. Multiple wildcards can be used in a single pattern. Matching is case insensitive. Cannot be used together with 'filter' or 'ids'. +- `description` (String) Filter policies by description. All provided filter attributes must match for a policy to be returned (omitted attributes are ignored). Supports wildcard matching with '*' where '*' matches any sequence of characters until the end of the string or until the next literal character in the pattern is found. Multiple wildcards can be used in a single pattern. Matching is case insensitive. Cannot be used together with 'filter' or 'ids'. +- `enabled` (Boolean) Filter policies by enabled status. All provided filter attributes must match for a policy to be returned (omitted attributes are ignored). Cannot be used together with 'filter' or 'ids'. +- `filter` (String) FQL filter to apply to the content update policies query. When specified, only policies matching the filter will be returned. Cannot be used together with 'ids' or other filter attributes. Example: `name:'*prod*'` +- `ids` (List of String) List of content update policy IDs to retrieve. When specified, only policies with matching IDs will be returned. Cannot be used together with 'filter' or other filter attributes. +- `modified_by` (String) Filter policies by the user who last modified them. All provided filter attributes must match for a policy to be returned (omitted attributes are ignored). Supports wildcard matching with '*' where '*' matches any sequence of characters until the end of the string or until the next literal character in the pattern is found. Multiple wildcards can be used in a single pattern. Matching is case insensitive. Cannot be used together with 'filter' or 'ids'. +- `name` (String) Filter policies by name. All provided filter attributes must match for a policy to be returned (omitted attributes are ignored). Supports wildcard matching with '*' where '*' matches any sequence of characters until the end of the string or until the next literal character in the pattern is found. Multiple wildcards can be used in a single pattern. Matching is case insensitive. Cannot be used together with 'filter' or 'ids'. +- `sort` (String) Sort order for the results. Valid values include field names with optional '.asc' or '.desc' suffix. Example: 'name.asc', 'precedence.desc' + +### Read-Only + +- `policies` (Attributes List) The list of content update policies (see [below for nested schema](#nestedatt--policies)) + + +### Nested Schema for `policies` + +Read-Only: + +- `created_by` (String) User who created the policy +- `created_timestamp` (String) Timestamp when the policy was created +- `description` (String) The content update policy description +- `enabled` (Boolean) Whether the content update policy is enabled +- `host_groups` (List of String) List of host group IDs assigned to the policy +- `id` (String) The content update policy ID +- `modified_by` (String) User who last modified the policy +- `modified_timestamp` (String) Timestamp when the policy was last modified +- `name` (String) The content update policy name +- `rapid_response` (Attributes) Ring assignment settings for rapid response allow/block listing content category (see [below for nested schema](#nestedatt--policies--rapid_response)) +- `sensor_operations` (Attributes) Ring assignment settings for sensor operations content category (see [below for nested schema](#nestedatt--policies--sensor_operations)) +- `system_critical` (Attributes) Ring assignment settings for system critical content category (see [below for nested schema](#nestedatt--policies--system_critical)) +- `vulnerability_management` (Attributes) Ring assignment settings for vulnerability management content category (see [below for nested schema](#nestedatt--policies--vulnerability_management)) + + +### Nested Schema for `policies.rapid_response` + +Read-Only: + +- `delay_hours` (Number) Delay in hours when using 'ga' ring assignment +- `pinned_content_version` (String) Pinned content version for the content category +- `ring_assignment` (String) Ring assignment for the content category (ga, ea, pause) + + + +### Nested Schema for `policies.sensor_operations` + +Read-Only: + +- `delay_hours` (Number) Delay in hours when using 'ga' ring assignment +- `pinned_content_version` (String) Pinned content version for the content category +- `ring_assignment` (String) Ring assignment for the content category (ga, ea, pause) + + + +### Nested Schema for `policies.system_critical` + +Read-Only: + +- `delay_hours` (Number) Delay in hours when using 'ga' ring assignment +- `pinned_content_version` (String) Pinned content version for the content category +- `ring_assignment` (String) Ring assignment for the content category (ga, ea) + + + +### Nested Schema for `policies.vulnerability_management` + +Read-Only: + +- `delay_hours` (Number) Delay in hours when using 'ga' ring assignment +- `pinned_content_version` (String) Pinned content version for the content category +- `ring_assignment` (String) Ring assignment for the content category (ga, ea, pause) diff --git a/docs/resources/content_update_policy.md b/docs/resources/content_update_policy.md index 3a57747e..68e3f7a7 100644 --- a/docs/resources/content_update_policy.md +++ b/docs/resources/content_update_policy.md @@ -5,7 +5,7 @@ description: |- This resource allows management of content update policies in the CrowdStrike Falcon platform. Content update policies control how and when CrowdStrike content updates are deployed to hosts. API Scopes The following API scopes are required: - Content update policies | Read & Write + Content Update Policy | Read & Write --- # crowdstrike_content_update_policy (Resource) @@ -16,7 +16,7 @@ This resource allows management of content update policies in the CrowdStrike Fa The following API scopes are required: -- Content update policies | Read & Write +- Content Update Policy | Read & Write ## Example Usage diff --git a/docs/resources/content_update_policy_precedence.md b/docs/resources/content_update_policy_precedence.md index 6407d2e6..1eccdedd 100644 --- a/docs/resources/content_update_policy_precedence.md +++ b/docs/resources/content_update_policy_precedence.md @@ -5,7 +5,7 @@ description: |- This resource allows you to set the precedence of Content Update Policies based on the order of IDs. API Scopes The following API scopes are required: - Content update policies | Read & Write + Content Update Policy | Read & Write --- # crowdstrike_content_update_policy_precedence (Resource) @@ -16,7 +16,7 @@ This resource allows you to set the precedence of Content Update Policies based The following API scopes are required: -- Content update policies | Read & Write +- Content Update Policy | Read & Write ## Example Usage diff --git a/docs/resources/default_content_update_policy.md b/docs/resources/default_content_update_policy.md index e75fd4af..8f5543ff 100644 --- a/docs/resources/default_content_update_policy.md +++ b/docs/resources/default_content_update_policy.md @@ -5,7 +5,7 @@ description: |- This resource allows management of the default content update policy in the CrowdStrike Falcon platform. Destruction of this resource will not delete the default content update policy or remove any configured settings. API Scopes The following API scopes are required: - Content update policies | Read & Write + Content Update Policy | Read & Write --- # crowdstrike_default_content_update_policy (Resource) @@ -16,7 +16,7 @@ This resource allows management of the default content update policy in the Crow The following API scopes are required: -- Content update policies | Read & Write +- Content Update Policy | Read & Write ## Example Usage diff --git a/examples/data-sources/crowdstrike_content_update_policies/data-source.tf b/examples/data-sources/crowdstrike_content_update_policies/data-source.tf new file mode 100644 index 00000000..6abb50ee --- /dev/null +++ b/examples/data-sources/crowdstrike_content_update_policies/data-source.tf @@ -0,0 +1,33 @@ +terraform { + required_providers { + crowdstrike = { + source = "registry.terraform.io/crowdstrike/crowdstrike" + } + } +} + +provider "crowdstrike" { + cloud = "us-2" +} + +# Get all content update policies +data "crowdstrike_content_update_policies" "all" {} + +# Get enabled policies +data "crowdstrike_content_update_policies" "enabled" { + enabled = true +} + +# Use FQL filter with sorting +data "crowdstrike_content_update_policies" "filtered_and_sorted" { + filter = "enabled:true+name:'*prod*'" + sort = "name.asc" +} + +# Get policies by ID +data "crowdstrike_content_update_policies" "specific" { + ids = [ + "policy-id-1", + "policy-id-2" + ] +} diff --git a/internal/content_update_policy/content_category_versions_data_source.go b/internal/content_update_policy/content_category_versions_data_source.go index 773d072a..f021398f 100644 --- a/internal/content_update_policy/content_category_versions_data_source.go +++ b/internal/content_update_policy/content_category_versions_data_source.go @@ -54,15 +54,7 @@ func (d *contentCategoryVersionsDataSource) Schema( resp.Schema = schema.Schema{ MarkdownDescription: fmt.Sprintf( "Content Update Policy --- This data source provides information about available content category versions for pinning in content update policies.\n\n%s", - scopes.GenerateScopeDescription( - []scopes.Scope{ - { - Name: "Content update policy", - Read: true, - Write: false, - }, - }, - ), + scopes.GenerateScopeDescription(apiScopesRead), ), Attributes: map[string]schema.Attribute{ "sensor_operations": schema.ListAttribute{ diff --git a/internal/content_update_policy/content_update_policies_data_source.go b/internal/content_update_policy/content_update_policies_data_source.go new file mode 100644 index 00000000..fdfd959f --- /dev/null +++ b/internal/content_update_policy/content_update_policies_data_source.go @@ -0,0 +1,604 @@ +package contentupdatepolicy + +import ( + "context" + "fmt" + + "github.com/crowdstrike/gofalcon/falcon/client" + "github.com/crowdstrike/gofalcon/falcon/client/content_update_policies" + "github.com/crowdstrike/gofalcon/falcon/models" + fwvalidators "github.com/crowdstrike/terraform-provider-crowdstrike/internal/framework/validators" + hostgroups "github.com/crowdstrike/terraform-provider-crowdstrike/internal/host_groups" + "github.com/crowdstrike/terraform-provider-crowdstrike/internal/tferrors" + "github.com/crowdstrike/terraform-provider-crowdstrike/internal/utils" + "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/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +const ( + dataSourceDocumentationSection = "Content Update Policies" + dataSourceMarkdownDescription = "This data source provides information about content update policies in Falcon." +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ datasource.DataSource = &contentUpdatePoliciesDataSource{} + _ datasource.DataSourceWithConfigure = &contentUpdatePoliciesDataSource{} + _ datasource.DataSourceWithValidateConfig = &contentUpdatePoliciesDataSource{} +) + +// Configure adds the provider configured client to the data source. +func (d *contentUpdatePoliciesDataSource) Configure( + _ context.Context, + req datasource.ConfigureRequest, + resp *datasource.ConfigureResponse, +) { + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*client.CrowdStrikeAPISpecification) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf( + "Expected *client.CrowdStrikeAPISpecification, got: %T. Please report this issue to the provider developers.", + req.ProviderData, + ), + ) + return + } + + d.client = client +} + +// contentUpdatePoliciesDataSource is the data source implementation. +type contentUpdatePoliciesDataSource struct { + client *client.CrowdStrikeAPISpecification +} + +type policyDataModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + Enabled types.Bool `tfsdk:"enabled"` + CreatedBy types.String `tfsdk:"created_by"` + CreatedTimestamp types.String `tfsdk:"created_timestamp"` + ModifiedBy types.String `tfsdk:"modified_by"` + ModifiedTimestamp types.String `tfsdk:"modified_timestamp"` + HostGroups types.List `tfsdk:"host_groups"` + SensorOperations types.Object `tfsdk:"sensor_operations"` + SystemCritical types.Object `tfsdk:"system_critical"` + VulnerabilityManagement types.Object `tfsdk:"vulnerability_management"` + RapidResponse types.Object `tfsdk:"rapid_response"` +} + +func (m policyDataModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "id": types.StringType, + "name": types.StringType, + "description": types.StringType, + "enabled": types.BoolType, + "created_by": types.StringType, + "created_timestamp": types.StringType, + "modified_by": types.StringType, + "modified_timestamp": types.StringType, + "host_groups": types.ListType{ElemType: types.StringType}, + "sensor_operations": types.ObjectType{AttrTypes: ringAssignmentModel{}.AttributeTypes()}, + "system_critical": types.ObjectType{AttrTypes: ringAssignmentModel{}.AttributeTypes()}, + "vulnerability_management": types.ObjectType{AttrTypes: ringAssignmentModel{}.AttributeTypes()}, + "rapid_response": types.ObjectType{AttrTypes: ringAssignmentModel{}.AttributeTypes()}, + } +} + +type ContentUpdatePoliciesDataSourceModel struct { + Filter types.String `tfsdk:"filter"` + IDs types.List `tfsdk:"ids"` + Sort types.String `tfsdk:"sort"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + Enabled types.Bool `tfsdk:"enabled"` + CreatedBy types.String `tfsdk:"created_by"` + ModifiedBy types.String `tfsdk:"modified_by"` + Policies types.List `tfsdk:"policies"` +} + +func (m *ContentUpdatePoliciesDataSourceModel) wrap(ctx context.Context, policies []*models.ContentUpdatePolicyV1) diag.Diagnostics { + var diags diag.Diagnostics + policyModels := make([]policyDataModel, 0, len(policies)) + for _, policy := range policies { + if policy == nil { + continue + } + + policyModel := policyDataModel{} + + policyModel.ID = types.StringPointerValue(policy.ID) + policyModel.Name = types.StringPointerValue(policy.Name) + policyModel.Description = types.StringPointerValue(policy.Description) + policyModel.Enabled = types.BoolPointerValue(policy.Enabled) + policyModel.CreatedBy = types.StringPointerValue(policy.CreatedBy) + policyModel.CreatedTimestamp = types.StringValue(policy.CreatedTimestamp.String()) + policyModel.ModifiedBy = types.StringPointerValue(policy.ModifiedBy) + policyModel.ModifiedTimestamp = types.StringValue(policy.ModifiedTimestamp.String()) + + hostGroups, hostGroupDiags := hostgroups.ConvertHostGroupsToList(ctx, policy.Groups) + diags.Append(hostGroupDiags...) + if diags.HasError() { + return diags + } + policyModel.HostGroups = hostGroups + + if policy.Settings != nil && policy.Settings.RingAssignmentSettings != nil { + var sensorOperations ringAssignmentModel + var systemCritical ringAssignmentModel + var vulnerabilityManagement ringAssignmentModel + var rapidResponse ringAssignmentModel + + for _, setting := range policy.Settings.RingAssignmentSettings { + if setting == nil || setting.ID == nil { + continue + } + switch *setting.ID { + case "sensor_operations": + sensorOperations.wrap(setting) + case "system_critical": + systemCritical.wrap(setting) + case "vulnerability_management": + vulnerabilityManagement.wrap(setting) + case "rapid_response_al_bl_listing": + rapidResponse.wrap(setting) + } + } + + sensorOperationsObj, sensorOpsDiags := utils.ConvertModelToTerraformObject(ctx, &sensorOperations) + diags.Append(sensorOpsDiags...) + policyModel.SensorOperations = sensorOperationsObj + + systemCriticalObj, systemCritDiags := utils.ConvertModelToTerraformObject(ctx, &systemCritical) + diags.Append(systemCritDiags...) + policyModel.SystemCritical = systemCriticalObj + + vulnMgmtObj, vulnMgmtDiags := utils.ConvertModelToTerraformObject(ctx, &vulnerabilityManagement) + diags.Append(vulnMgmtDiags...) + policyModel.VulnerabilityManagement = vulnMgmtObj + + rapidResponseObj, rapidRespDiags := utils.ConvertModelToTerraformObject(ctx, &rapidResponse) + diags.Append(rapidRespDiags...) + policyModel.RapidResponse = rapidResponseObj + } + + policyModels = append(policyModels, policyModel) + } + + m.Policies = utils.SliceToListTypeObject(ctx, policyModels, policyDataModel{}.AttributeTypes(), &diags) + return diags +} + +// hasIndividualFilters checks if any of the individual filter attributes are set. +func (m ContentUpdatePoliciesDataSourceModel) hasIndividualFilters() bool { + return utils.IsKnown(m.Name) || + utils.IsKnown(m.Description) || + utils.IsKnown(m.Enabled) || + utils.IsKnown(m.CreatedBy) || + utils.IsKnown(m.ModifiedBy) +} + +// NewContentUpdatePoliciesDataSource is a helper function to simplify the provider implementation. +func NewContentUpdatePoliciesDataSource() datasource.DataSource { + return &contentUpdatePoliciesDataSource{} +} + +// Metadata returns the data source type name. +func (d *contentUpdatePoliciesDataSource) Metadata( + _ context.Context, + req datasource.MetadataRequest, + resp *datasource.MetadataResponse, +) { + resp.TypeName = req.ProviderTypeName + "_content_update_policies" +} + +// Schema defines the schema for the data source. +func (d *contentUpdatePoliciesDataSource) Schema( + _ context.Context, + _ datasource.SchemaRequest, + resp *datasource.SchemaResponse, +) { + resp.Schema = schema.Schema{ + MarkdownDescription: utils.MarkdownDescription( + dataSourceDocumentationSection, + dataSourceMarkdownDescription, + apiScopesRead, + ), + Attributes: map[string]schema.Attribute{ + "filter": schema.StringAttribute{ + Optional: true, + Description: "FQL filter to apply to the content update policies query. When specified, only policies matching the filter will be returned. Cannot be used together with 'ids' or other filter attributes. Example: `name:'*prod*'`", + Validators: []validator.String{ + fwvalidators.StringNotWhitespace(), + }, + }, + "ids": schema.ListAttribute{ + Optional: true, + ElementType: types.StringType, + Description: "List of content update policy IDs to retrieve. When specified, only policies with matching IDs will be returned. Cannot be used together with 'filter' or other filter attributes.", + Validators: []validator.List{ + listvalidator.ValueStringsAre( + stringvalidator.LengthBetween(32, 32), + ), + listvalidator.SizeAtLeast(1), + listvalidator.UniqueValues(), + }, + }, + "sort": schema.StringAttribute{ + Optional: true, + Description: "Sort order for the results. Valid values include field names with optional '.asc' or '.desc' suffix. Example: 'name.asc', 'precedence.desc'", + Validators: []validator.String{ + fwvalidators.StringNotWhitespace(), + }, + }, + "name": schema.StringAttribute{ + Optional: true, + Description: "Filter policies by name. All provided filter attributes must match for a policy to be returned (omitted attributes are ignored). Supports wildcard matching with '*' where '*' matches any sequence of characters until the end of the string or until the next literal character in the pattern is found. Multiple wildcards can be used in a single pattern. Matching is case insensitive. Cannot be used together with 'filter' or 'ids'.", + Validators: []validator.String{ + fwvalidators.StringNotWhitespace(), + }, + }, + "description": schema.StringAttribute{ + Optional: true, + Description: "Filter policies by description. All provided filter attributes must match for a policy to be returned (omitted attributes are ignored). Supports wildcard matching with '*' where '*' matches any sequence of characters until the end of the string or until the next literal character in the pattern is found. Multiple wildcards can be used in a single pattern. Matching is case insensitive. Cannot be used together with 'filter' or 'ids'.", + Validators: []validator.String{ + fwvalidators.StringNotWhitespace(), + }, + }, + "enabled": schema.BoolAttribute{ + Optional: true, + Description: "Filter policies by enabled status. All provided filter attributes must match for a policy to be returned (omitted attributes are ignored). Cannot be used together with 'filter' or 'ids'.", + }, + "created_by": schema.StringAttribute{ + Optional: true, + Description: "Filter policies by the user who created them. All provided filter attributes must match for a policy to be returned (omitted attributes are ignored). Supports wildcard matching with '*' where '*' matches any sequence of characters until the end of the string or until the next literal character in the pattern is found. Multiple wildcards can be used in a single pattern. Matching is case insensitive. Cannot be used together with 'filter' or 'ids'.", + Validators: []validator.String{ + fwvalidators.StringNotWhitespace(), + }, + }, + "modified_by": schema.StringAttribute{ + Optional: true, + Description: "Filter policies by the user who last modified them. All provided filter attributes must match for a policy to be returned (omitted attributes are ignored). Supports wildcard matching with '*' where '*' matches any sequence of characters until the end of the string or until the next literal character in the pattern is found. Multiple wildcards can be used in a single pattern. Matching is case insensitive. Cannot be used together with 'filter' or 'ids'.", + Validators: []validator.String{ + fwvalidators.StringNotWhitespace(), + }, + }, + "policies": schema.ListNestedAttribute{ + Computed: true, + Description: "The list of content update policies", + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "The content update policy ID", + }, + "name": schema.StringAttribute{ + Computed: true, + Description: "The content update policy name", + }, + "description": schema.StringAttribute{ + Computed: true, + Description: "The content update policy description", + }, + "enabled": schema.BoolAttribute{ + Computed: true, + Description: "Whether the content update policy is enabled", + }, + "created_by": schema.StringAttribute{ + Computed: true, + Description: "User who created the policy", + }, + "created_timestamp": schema.StringAttribute{ + Computed: true, + Description: "Timestamp when the policy was created", + }, + "modified_by": schema.StringAttribute{ + Computed: true, + Description: "User who last modified the policy", + }, + "modified_timestamp": schema.StringAttribute{ + Computed: true, + Description: "Timestamp when the policy was last modified", + }, + "host_groups": schema.ListAttribute{ + Computed: true, + ElementType: types.StringType, + Description: "List of host group IDs assigned to the policy", + }, + "sensor_operations": schema.SingleNestedAttribute{ + Computed: true, + Description: "Ring assignment settings for sensor operations content category", + Attributes: map[string]schema.Attribute{ + "ring_assignment": schema.StringAttribute{ + Computed: true, + Description: "Ring assignment for the content category (ga, ea, pause)", + }, + "delay_hours": schema.Int64Attribute{ + Computed: true, + Description: "Delay in hours when using 'ga' ring assignment", + }, + "pinned_content_version": schema.StringAttribute{ + Computed: true, + Description: "Pinned content version for the content category", + }, + }, + }, + "system_critical": schema.SingleNestedAttribute{ + Computed: true, + Description: "Ring assignment settings for system critical content category", + Attributes: map[string]schema.Attribute{ + "ring_assignment": schema.StringAttribute{ + Computed: true, + Description: "Ring assignment for the content category (ga, ea)", + }, + "delay_hours": schema.Int64Attribute{ + Computed: true, + Description: "Delay in hours when using 'ga' ring assignment", + }, + "pinned_content_version": schema.StringAttribute{ + Computed: true, + Description: "Pinned content version for the content category", + }, + }, + }, + "vulnerability_management": schema.SingleNestedAttribute{ + Computed: true, + Description: "Ring assignment settings for vulnerability management content category", + Attributes: map[string]schema.Attribute{ + "ring_assignment": schema.StringAttribute{ + Computed: true, + Description: "Ring assignment for the content category (ga, ea, pause)", + }, + "delay_hours": schema.Int64Attribute{ + Computed: true, + Description: "Delay in hours when using 'ga' ring assignment", + }, + "pinned_content_version": schema.StringAttribute{ + Computed: true, + Description: "Pinned content version for the content category", + }, + }, + }, + "rapid_response": schema.SingleNestedAttribute{ + Computed: true, + Description: "Ring assignment settings for rapid response allow/block listing content category", + Attributes: map[string]schema.Attribute{ + "ring_assignment": schema.StringAttribute{ + Computed: true, + Description: "Ring assignment for the content category (ga, ea, pause)", + }, + "delay_hours": schema.Int64Attribute{ + Computed: true, + Description: "Delay in hours when using 'ga' ring assignment", + }, + "pinned_content_version": schema.StringAttribute{ + Computed: true, + Description: "Pinned content version for the content category", + }, + }, + }, + }, + }, + }, + }, + } +} + +func (d *contentUpdatePoliciesDataSource) ValidateConfig(ctx context.Context, req datasource.ValidateConfigRequest, resp *datasource.ValidateConfigResponse) { + var data ContentUpdatePoliciesDataSourceModel + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + hasFilter := utils.IsKnown(data.Filter) && data.Filter.ValueString() != "" + hasIDs := utils.IsKnown(data.IDs) && len(data.IDs.Elements()) > 0 + + filterCount := 0 + if hasFilter { + filterCount++ + } + if hasIDs { + filterCount++ + } + if data.hasIndividualFilters() { + filterCount++ + } + + if filterCount > 1 { + resp.Diagnostics.AddError( + "Invalid Attribute Combination", + "Cannot specify 'filter', 'ids', and individual filter attributes (name, description, enabled, created_by, modified_by) together. Please use only one filtering method: either 'filter' for FQL queries, 'ids' for specific IDs, or individual filter attributes.", + ) + } +} + +// getContentUpdatePolicies returns all content update policies matching filter. +func (d *contentUpdatePoliciesDataSource) getContentUpdatePolicies( + ctx context.Context, + filter string, + sort string, +) ([]*models.ContentUpdatePolicyV1, diag.Diagnostics) { + var diags diag.Diagnostics + var allPolicies []*models.ContentUpdatePolicyV1 + + tflog.Debug( + ctx, + "[datasource] Getting all content update policies", + ) + + limit := int64(5000) + offset := int64(0) + + for { + params := &content_update_policies.QueryCombinedContentUpdatePoliciesParams{ + Context: ctx, + Limit: &limit, + Offset: &offset, + } + + if filter != "" { + params.Filter = &filter + } + + if sort != "" { + params.Sort = &sort + } + + res, err := d.client.ContentUpdatePolicies.QueryCombinedContentUpdatePolicies(params) + if err != nil { + diags.Append(tferrors.NewOperationError(tferrors.Read, err)) + return allPolicies, diags + } + + if res == nil || res.Payload == nil || len(res.Payload.Resources) == 0 { + tflog.Debug(ctx, "[datasource] No more content update policies to retrieve", + map[string]interface{}{ + "total_retrieved": len(allPolicies), + }) + break + } + + allPolicies = append(allPolicies, res.Payload.Resources...) + tflog.Debug(ctx, "[datasource] Retrieved page of content update policies", + map[string]interface{}{ + "page_count": len(res.Payload.Resources), + "total_count": len(allPolicies), + "offset": offset, + }) + + if res.Payload.Meta == nil || res.Payload.Meta.Pagination == nil || + res.Payload.Meta.Pagination.Offset == nil || res.Payload.Meta.Pagination.Total == nil { + tflog.Warn(ctx, "Missing pagination metadata in API response, using offset+limit for next page", + map[string]interface{}{ + "meta": res.Payload.Meta, + }) + offset += limit + continue + } + + offset = int64(*res.Payload.Meta.Pagination.Offset) + if offset >= *res.Payload.Meta.Pagination.Total { + tflog.Info(ctx, "[datasource] Pagination complete", + map[string]interface{}{ + "total_retrieved": len(allPolicies), + "total_available": *res.Payload.Meta.Pagination.Total, + }) + break + } + } + + return allPolicies, diags +} + +// Read refreshes the Terraform state with the latest data. +func (d *contentUpdatePoliciesDataSource) Read( + ctx context.Context, + req datasource.ReadRequest, + resp *datasource.ReadResponse, +) { + var data ContentUpdatePoliciesDataSourceModel + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + policies, diags := d.getContentUpdatePolicies(ctx, data.Filter.ValueString(), data.Sort.ValueString()) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + if utils.IsKnown(data.IDs) { + requestedIDs := utils.ListTypeAs[string](ctx, data.IDs, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + + policies = FilterPoliciesByIDs(policies, requestedIDs) + } + + if data.hasIndividualFilters() { + policies = FilterPoliciesByAttributes(policies, &data) + } + + resp.Diagnostics.Append(data.wrap(ctx, policies)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func FilterPoliciesByIDs(policies []*models.ContentUpdatePolicyV1, requestedIDs []string) []*models.ContentUpdatePolicyV1 { + idMap := make(map[string]bool, len(requestedIDs)) + for _, id := range requestedIDs { + idMap[id] = true + } + + filtered := make([]*models.ContentUpdatePolicyV1, 0, len(requestedIDs)) + for _, policy := range policies { + if policy != nil && policy.ID != nil && idMap[*policy.ID] { + filtered = append(filtered, policy) + if len(filtered) == len(requestedIDs) { + break + } + } + } + return filtered +} + +func FilterPoliciesByAttributes(policies []*models.ContentUpdatePolicyV1, filters *ContentUpdatePoliciesDataSourceModel) []*models.ContentUpdatePolicyV1 { + filtered := make([]*models.ContentUpdatePolicyV1, 0, len(policies)) + for _, policy := range policies { + if policy == nil { + continue + } + + if !filters.Name.IsNull() { + if policy.Name == nil || !utils.MatchesWildcard(*policy.Name, filters.Name.ValueString()) { + continue + } + } + + if !filters.Description.IsNull() { + if policy.Description == nil || !utils.MatchesWildcard(*policy.Description, filters.Description.ValueString()) { + continue + } + } + + if !filters.CreatedBy.IsNull() { + if policy.CreatedBy == nil || !utils.MatchesWildcard(*policy.CreatedBy, filters.CreatedBy.ValueString()) { + continue + } + } + + if !filters.ModifiedBy.IsNull() { + if policy.ModifiedBy == nil || !utils.MatchesWildcard(*policy.ModifiedBy, filters.ModifiedBy.ValueString()) { + continue + } + } + + if !filters.Enabled.IsNull() { + if policy.Enabled == nil || *policy.Enabled != filters.Enabled.ValueBool() { + continue + } + } + + filtered = append(filtered, policy) + } + return filtered +} diff --git a/internal/content_update_policy/content_update_policies_data_source_test.go b/internal/content_update_policy/content_update_policies_data_source_test.go new file mode 100644 index 00000000..d2b0a36c --- /dev/null +++ b/internal/content_update_policy/content_update_policies_data_source_test.go @@ -0,0 +1,1029 @@ +package contentupdatepolicy_test + +import ( + "fmt" + "regexp" + "testing" + + "github.com/crowdstrike/gofalcon/falcon/models" + "github.com/crowdstrike/terraform-provider-crowdstrike/internal/acctest" + contentupdatepolicy "github.com/crowdstrike/terraform-provider-crowdstrike/internal/content_update_policy" + "github.com/crowdstrike/terraform-provider-crowdstrike/internal/utils" + "github.com/hashicorp/terraform-plugin-framework/types" + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/stretchr/testify/assert" +) + +func TestAccContentUpdatePoliciesDataSource_Basic(t *testing.T) { + resourceName := "data.crowdstrike_content_update_policies.test" + + resource.ParallelTest(t, resource.TestCase{ + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + PreCheck: func() { acctest.PreCheck(t) }, + Steps: []resource.TestStep{ + { + Config: testAccContentUpdatePoliciesDataSourceConfigBasic(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceName, "policies.#"), + resource.TestCheckResourceAttrSet(resourceName, "policies.0.id"), + resource.TestCheckResourceAttrSet(resourceName, "policies.0.name"), + resource.TestCheckResourceAttrSet(resourceName, "policies.0.description"), + resource.TestCheckResourceAttrSet(resourceName, "policies.0.enabled"), + resource.TestCheckResourceAttrSet(resourceName, "policies.0.created_by"), + resource.TestCheckResourceAttrSet(resourceName, "policies.0.created_timestamp"), + resource.TestCheckResourceAttrSet(resourceName, "policies.0.modified_by"), + resource.TestCheckResourceAttrSet(resourceName, "policies.0.modified_timestamp"), + resource.TestCheckResourceAttrSet(resourceName, "policies.0.host_groups.#"), + ), + }, + }, + }) +} + +func TestAccContentUpdatePoliciesDataSource_WithFilter(t *testing.T) { + resourceName := "data.crowdstrike_content_update_policies.test" + + resource.ParallelTest(t, resource.TestCase{ + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + PreCheck: func() { acctest.PreCheck(t) }, + Steps: []resource.TestStep{ + { + Config: testAccContentUpdatePoliciesDataSourceConfigWithFilterEnabled(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceName, "policies.#"), + resource.TestCheckResourceAttr(resourceName, "policies.0.enabled", "true"), + ), + }, + { + Config: testAccContentUpdatePoliciesDataSourceConfigWithFilterComplex(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceName, "policies.#"), + ), + }, + }, + }) +} + +func TestAccContentUpdatePoliciesDataSource_WithIDs(t *testing.T) { + resourceName := "data.crowdstrike_content_update_policies.test" + + resource.ParallelTest(t, resource.TestCase{ + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + PreCheck: func() { acctest.PreCheck(t) }, + Steps: []resource.TestStep{ + { + Config: testAccContentUpdatePoliciesDataSourceConfigWithIDs(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceName, "policies.#"), + resource.TestMatchResourceAttr(resourceName, "policies.#", regexp.MustCompile(`^[12]$`)), + ), + }, + }, + }) +} + +func TestAccContentUpdatePoliciesDataSource_IndividualFilters(t *testing.T) { + resourceName := "data.crowdstrike_content_update_policies.test" + + testCases := map[string]struct { + configFunc func() string + checkFunc resource.TestCheckFunc + }{ + "enabled": { + configFunc: testAccContentUpdatePoliciesDataSourceConfigWithEnabledFilter, + checkFunc: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceName, "policies.#"), + resource.TestCheckResourceAttr(resourceName, "policies.0.enabled", "true"), + ), + }, + "name": {configFunc: testAccContentUpdatePoliciesDataSourceConfigWithNameFilter}, + "description": {configFunc: testAccContentUpdatePoliciesDataSourceConfigWithDescriptionFilter}, + "created_by": {configFunc: testAccContentUpdatePoliciesDataSourceConfigWithCreatedByFilter}, + "modified_by": {configFunc: testAccContentUpdatePoliciesDataSourceConfigWithModifiedByFilter}, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + checkFunc := tc.checkFunc + if checkFunc == nil { + checkFunc = resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceName, "policies.#"), + ) + } + + resource.ParallelTest(t, resource.TestCase{ + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + PreCheck: func() { acctest.PreCheck(t) }, + Steps: []resource.TestStep{ + { + Config: tc.configFunc(), + Check: checkFunc, + }, + }, + }) + }) + } +} + +func TestAccContentUpdatePoliciesDataSource_ValidationErrors(t *testing.T) { + testCases := map[string]struct { + configFunc func() string + expectError *regexp.Regexp + }{ + "filter_with_ids": { + configFunc: testAccContentUpdatePoliciesDataSourceConfigValidationFilterIDs, + expectError: regexp.MustCompile("Invalid Attribute Combination"), + }, + "filter_with_individual": { + configFunc: testAccContentUpdatePoliciesDataSourceConfigValidationFilterIndividual, + expectError: regexp.MustCompile("Invalid Attribute Combination"), + }, + "ids_with_individual": { + configFunc: testAccContentUpdatePoliciesDataSourceConfigValidationIDsIndividual, + expectError: regexp.MustCompile("Invalid Attribute Combination"), + }, + "all_three": { + configFunc: testAccContentUpdatePoliciesDataSourceConfigValidationAllThree, + expectError: regexp.MustCompile("Invalid Attribute Combination"), + }, + "multiple_filter_methods": { + configFunc: testAccContentUpdatePoliciesDataSourceConfigValidationMultipleFilter, + expectError: regexp.MustCompile("Invalid Attribute Combination"), + }, + "filter_with_created_by": { + configFunc: testAccContentUpdatePoliciesDataSourceConfigValidationFilterCreatedBy, + expectError: regexp.MustCompile("Invalid Attribute Combination"), + }, + "ids_with_modified_by": { + configFunc: testAccContentUpdatePoliciesDataSourceConfigValidationIDsModifiedBy, + expectError: regexp.MustCompile("Invalid Attribute Combination"), + }, + "filter_with_description": { + configFunc: testAccContentUpdatePoliciesDataSourceConfigValidationFilterDescription, + expectError: regexp.MustCompile("Invalid Attribute Combination"), + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + PreCheck: func() { acctest.PreCheck(t) }, + Steps: []resource.TestStep{ + { + Config: tc.configFunc(), + ExpectError: tc.expectError, + }, + }, + }) + }) + } +} + +func TestAccContentUpdatePoliciesDataSource_404Handling(t *testing.T) { + resourceName := "data.crowdstrike_content_update_policies.test" + + resource.ParallelTest(t, resource.TestCase{ + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + PreCheck: func() { acctest.PreCheck(t) }, + Steps: []resource.TestStep{ + { + Config: testAccContentUpdatePoliciesDataSourceConfig404NonExistentID(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "policies.#", "0"), + ), + }, + { + Config: testAccContentUpdatePoliciesDataSourceConfig404PartialResults(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "policies.#", "1"), + resource.TestCheckResourceAttrSet(resourceName, "policies.0.id"), + resource.TestCheckResourceAttrSet(resourceName, "policies.0.name"), + ), + }, + }, + }) +} + +func TestAccContentUpdatePoliciesDataSource_AllAttributes(t *testing.T) { + resourceName := "data.crowdstrike_content_update_policies.test" + + resource.ParallelTest(t, resource.TestCase{ + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + PreCheck: func() { acctest.PreCheck(t) }, + Steps: []resource.TestStep{ + { + Config: testAccContentUpdatePoliciesDataSourceConfigBasic(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceName, "policies.0.id"), + resource.TestCheckResourceAttrSet(resourceName, "policies.0.name"), + resource.TestCheckResourceAttrSet(resourceName, "policies.0.enabled"), + resource.TestCheckResourceAttrSet(resourceName, "policies.0.created_by"), + resource.TestCheckResourceAttrSet(resourceName, "policies.0.created_timestamp"), + resource.TestCheckResourceAttrSet(resourceName, "policies.0.modified_by"), + resource.TestCheckResourceAttrSet(resourceName, "policies.0.modified_timestamp"), + resource.TestCheckResourceAttrSet(resourceName, "policies.0.host_groups.#"), + resource.TestCheckResourceAttrSet(resourceName, "policies.0.sensor_operations.ring_assignment"), + resource.TestCheckResourceAttrSet(resourceName, "policies.0.system_critical.ring_assignment"), + resource.TestCheckResourceAttrSet(resourceName, "policies.0.vulnerability_management.ring_assignment"), + resource.TestCheckResourceAttrSet(resourceName, "policies.0.rapid_response.ring_assignment"), + ), + }, + }, + }) +} + +func TestAccContentUpdatePoliciesDataSource_ResourceMatch(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + dataSourceName := "data.crowdstrike_content_update_policies.test" + resourceName := "crowdstrike_content_update_policy.test" + + resource.ParallelTest(t, resource.TestCase{ + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + PreCheck: func() { acctest.PreCheck(t) }, + Steps: []resource.TestStep{ + { + Config: testAccContentUpdatePoliciesDataSourceConfigResourceMatch(rName), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair(resourceName, "id", dataSourceName, "policies.0.id"), + resource.TestCheckResourceAttrPair(resourceName, "name", dataSourceName, "policies.0.name"), + resource.TestCheckResourceAttrPair(resourceName, "enabled", dataSourceName, "policies.0.enabled"), + resource.TestCheckResourceAttrPair(resourceName, "description", dataSourceName, "policies.0.description"), + resource.TestCheckResourceAttrPair(resourceName, "host_groups.0", dataSourceName, "policies.0.host_groups.0"), + resource.TestCheckResourceAttrPair(resourceName, "sensor_operations.ring_assignment", dataSourceName, "policies.0.sensor_operations.ring_assignment"), + resource.TestCheckResourceAttrPair(resourceName, "sensor_operations.delay_hours", dataSourceName, "policies.0.sensor_operations.delay_hours"), + resource.TestCheckResourceAttrPair(resourceName, "sensor_operations.pinned_content_version", dataSourceName, "policies.0.sensor_operations.pinned_content_version"), + resource.TestCheckResourceAttrPair(resourceName, "system_critical.ring_assignment", dataSourceName, "policies.0.system_critical.ring_assignment"), + resource.TestCheckResourceAttrPair(resourceName, "system_critical.delay_hours", dataSourceName, "policies.0.system_critical.delay_hours"), + resource.TestCheckResourceAttrPair(resourceName, "vulnerability_management.ring_assignment", dataSourceName, "policies.0.vulnerability_management.ring_assignment"), + resource.TestCheckResourceAttrPair(resourceName, "vulnerability_management.pinned_content_version", dataSourceName, "policies.0.vulnerability_management.pinned_content_version"), + resource.TestCheckResourceAttrPair(resourceName, "rapid_response.ring_assignment", dataSourceName, "policies.0.rapid_response.ring_assignment"), + resource.TestCheckResourceAttrPair(resourceName, "rapid_response.pinned_content_version", dataSourceName, "policies.0.rapid_response.pinned_content_version"), + ), + }, + }, + }) +} + +func testAccContentUpdatePoliciesDataSourceConfigBasic() string { + return acctest.ProviderConfig + ` +data "crowdstrike_content_update_policies" "test" {} +` +} + +func testAccContentUpdatePoliciesDataSourceConfigWithFilterEnabled() string { + return acctest.ProviderConfig + ` +data "crowdstrike_content_update_policies" "test" { + filter = "enabled:true" +} +` +} + +func testAccContentUpdatePoliciesDataSourceConfigWithFilterComplex() string { + return acctest.ProviderConfig + ` +data "crowdstrike_content_update_policies" "test" { + filter = "name:'test'+enabled:true" +} +` +} + +func testAccContentUpdatePoliciesDataSourceConfigWithIDs() string { + return acctest.ProviderConfig + ` +data "crowdstrike_content_update_policies" "all" {} + +data "crowdstrike_content_update_policies" "test" { + ids = [ + data.crowdstrike_content_update_policies.all.policies[0].id, + length(data.crowdstrike_content_update_policies.all.policies) > 1 ? data.crowdstrike_content_update_policies.all.policies[1].id : data.crowdstrike_content_update_policies.all.policies[0].id + ] +} +` +} + +func testAccContentUpdatePoliciesDataSourceConfigWithEnabledFilter() string { + return acctest.ProviderConfig + ` +data "crowdstrike_content_update_policies" "test" { + enabled = true +} +` +} + +func testAccContentUpdatePoliciesDataSourceConfigWithNameFilter() string { + return acctest.ProviderConfig + ` +data "crowdstrike_content_update_policies" "test" { + name = "*policy*" +} +` +} + +func testAccContentUpdatePoliciesDataSourceConfigWithDescriptionFilter() string { + return acctest.ProviderConfig + ` +data "crowdstrike_content_update_policies" "test" { + description = "*protection*" +} +` +} + +func testAccContentUpdatePoliciesDataSourceConfigValidationFilterIDs() string { + return acctest.ProviderConfig + ` +data "crowdstrike_content_update_policies" "test" { + ids = ["00000000000000000000000000000001", "00000000000000000000000000000002"] + description = "*protection*" +} +` +} + +func testAccContentUpdatePoliciesDataSourceConfigValidationFilterIndividual() string { + return acctest.ProviderConfig + ` +data "crowdstrike_content_update_policies" "test" { + filter = "description:'test'" + name = "test" +} +` +} + +func testAccContentUpdatePoliciesDataSourceConfigValidationIDsIndividual() string { + return acctest.ProviderConfig + ` +data "crowdstrike_content_update_policies" "test" { + ids = ["00000000000000000000000000000001"] + enabled = true +} +` +} + +func testAccContentUpdatePoliciesDataSourceConfigValidationAllThree() string { + return acctest.ProviderConfig + ` +data "crowdstrike_content_update_policies" "test" { + ids = ["00000000000000000000000000000001"] + name = "test" +} +` +} + +func testAccContentUpdatePoliciesDataSourceConfigValidationMultipleFilter() string { + return acctest.ProviderConfig + ` +data "crowdstrike_content_update_policies" "test" { + filter = "name:'test'" + enabled = true + name = "MyPolicy" +} +` +} + +func testAccContentUpdatePoliciesDataSourceConfig404NonExistentID() string { + return acctest.ProviderConfig + ` +data "crowdstrike_content_update_policies" "test" { + ids = ["00000000000000000000000000000000"] +} +` +} + +func testAccContentUpdatePoliciesDataSourceConfig404PartialResults() string { + return acctest.ProviderConfig + ` +data "crowdstrike_content_update_policies" "all" {} + +data "crowdstrike_content_update_policies" "test" { + ids = [ + data.crowdstrike_content_update_policies.all.policies[0].id, + "00000000000000000000000000000000" + ] +} +` +} + +func testAccContentUpdatePoliciesDataSourceConfigResourceMatch(rName string) string { + return acctest.ProviderConfig + fmt.Sprintf(` +# Fetch available content category versions +data "crowdstrike_content_category_versions" "available" {} + +resource "crowdstrike_host_group" "test" { + name = %[1]q + description = "Test host group for data source acceptance test" + type = "staticByID" + host_ids = [] +} + +resource "crowdstrike_content_update_policy" "test" { + name = %[1]q + description = "Test policy for data source acceptance test with ring settings" + enabled = true + host_groups = [crowdstrike_host_group.test.id] + + sensor_operations = { + ring_assignment = "ga" + delay_hours = 24 + pinned_content_version = length(data.crowdstrike_content_category_versions.available.sensor_operations) > 0 ? data.crowdstrike_content_category_versions.available.sensor_operations[0] : null + } + + system_critical = { + ring_assignment = "ga" + delay_hours = 48 + } + + vulnerability_management = { + ring_assignment = "ga" + pinned_content_version = length(data.crowdstrike_content_category_versions.available.vulnerability_management) > 0 ? data.crowdstrike_content_category_versions.available.vulnerability_management[0] : null + } + + rapid_response = { + ring_assignment = "ea" + pinned_content_version = length(data.crowdstrike_content_category_versions.available.rapid_response) > 0 ? data.crowdstrike_content_category_versions.available.rapid_response[0] : null + } +} + +data "crowdstrike_content_update_policies" "test" { + ids = [crowdstrike_content_update_policy.test.id] + + depends_on = [crowdstrike_content_update_policy.test] +} +`, rName) +} + +func testAccContentUpdatePoliciesDataSourceConfigWithCreatedByFilter() string { + return acctest.ProviderConfig + ` +data "crowdstrike_content_update_policies" "all" {} + +data "crowdstrike_content_update_policies" "test" { + created_by = data.crowdstrike_content_update_policies.all.policies[0].created_by +} +` +} + +func testAccContentUpdatePoliciesDataSourceConfigWithModifiedByFilter() string { + return acctest.ProviderConfig + ` +data "crowdstrike_content_update_policies" "all" {} + +data "crowdstrike_content_update_policies" "test" { + modified_by = data.crowdstrike_content_update_policies.all.policies[0].modified_by +} +` +} + +func testAccContentUpdatePoliciesDataSourceConfigValidationFilterCreatedBy() string { + return acctest.ProviderConfig + ` +data "crowdstrike_content_update_policies" "test" { + ids = ["00000000000000000000000000000001"] + created_by = "testuser@example.com" +} +` +} + +func testAccContentUpdatePoliciesDataSourceConfigValidationIDsModifiedBy() string { + return acctest.ProviderConfig + ` +data "crowdstrike_content_update_policies" "test" { + ids = ["00000000000000000000000000000001"] + modified_by = "testuser@example.com" +} +` +} + +func testAccContentUpdatePoliciesDataSourceConfigValidationFilterDescription() string { + return acctest.ProviderConfig + ` +data "crowdstrike_content_update_policies" "test" { + ids = ["00000000000000000000000000000001"] + description = "*malware*" +} +` +} + +var ( + testBoolTrue = true + testBoolFalse = false +) + +var testPolicies = []*models.ContentUpdatePolicyV1{ + { + ID: utils.Addr("policy-001"), + Name: utils.Addr("Production Policy"), + Description: utils.Addr("malware protection"), + CreatedBy: utils.Addr("admin@example.com"), + ModifiedBy: utils.Addr("security@example.com"), + Enabled: &testBoolTrue, + }, + { + ID: utils.Addr("policy-002"), + Name: utils.Addr("Production Backup"), + Description: utils.Addr("malware protection enabled"), + CreatedBy: utils.Addr("admin@example.com"), + ModifiedBy: utils.Addr("admin@example.com"), + Enabled: &testBoolTrue, + }, + { + ID: utils.Addr("policy-003"), + Name: utils.Addr("Production Desktop"), + Description: utils.Addr("endpoint protection"), + CreatedBy: utils.Addr("user@example.com"), + ModifiedBy: utils.Addr("security@example.com"), + Enabled: &testBoolTrue, + }, + { + ID: utils.Addr("policy-004"), + Name: utils.Addr("Test Policy"), + Description: utils.Addr("malware detection"), + CreatedBy: utils.Addr("user@example.com"), + ModifiedBy: utils.Addr("user@example.com"), + Enabled: &testBoolFalse, + }, + { + ID: utils.Addr("policy-005"), + Name: utils.Addr("Test Environment"), + Description: utils.Addr("ransomware protection"), + CreatedBy: utils.Addr("admin@crowdstrike.com"), + ModifiedBy: utils.Addr("admin@crowdstrike.com"), + Enabled: &testBoolTrue, + }, + { + ID: utils.Addr("policy-006"), + Name: utils.Addr("Windows Policy"), + Description: utils.Addr("Windows protection"), + CreatedBy: utils.Addr("admin@example.com"), + ModifiedBy: utils.Addr("admin@example.com"), + Enabled: &testBoolTrue, + }, + { + ID: utils.Addr("policy-007"), + Name: utils.Addr("Linux Policy"), + Description: utils.Addr("Linux protection"), + CreatedBy: utils.Addr("user@example.com"), + ModifiedBy: utils.Addr("user@example.com"), + Enabled: &testBoolFalse, + }, + { + ID: utils.Addr("policy-008"), + Name: utils.Addr("PRODUCTION Server"), + Description: utils.Addr("Server protection"), + CreatedBy: utils.Addr("admin@example.com"), + ModifiedBy: utils.Addr("admin@example.com"), + Enabled: &testBoolTrue, + }, + { + ID: utils.Addr("policy-009"), + Name: utils.Addr("production server"), + Description: utils.Addr("Desktop protection"), + CreatedBy: utils.Addr("admin@example.com"), + ModifiedBy: utils.Addr("admin@example.com"), + Enabled: &testBoolFalse, + }, + { + ID: utils.Addr("policy-010"), + Name: nil, + Description: utils.Addr("Description with no name"), + CreatedBy: utils.Addr("admin@example.com"), + ModifiedBy: utils.Addr("admin@example.com"), + Enabled: &testBoolTrue, + }, + { + ID: utils.Addr("policy-011"), + Name: utils.Addr("Policy with no description"), + Description: nil, + CreatedBy: utils.Addr("admin@example.com"), + ModifiedBy: utils.Addr("admin@example.com"), + Enabled: &testBoolTrue, + }, + { + ID: utils.Addr("policy-012"), + Name: utils.Addr("Policy with no user info"), + Description: utils.Addr("Description C"), + CreatedBy: nil, + ModifiedBy: nil, + Enabled: &testBoolTrue, + }, +} + +func policiesByID(allPolicies []*models.ContentUpdatePolicyV1, ids ...string) []*models.ContentUpdatePolicyV1 { + result := make([]*models.ContentUpdatePolicyV1, 0, len(ids)) + policyMap := make(map[string]*models.ContentUpdatePolicyV1) + + for _, policy := range allPolicies { + if policy.ID != nil { + policyMap[*policy.ID] = policy + } + } + + for _, id := range ids { + if policy, ok := policyMap[id]; ok { + result = append(result, policy) + } + } + + return result +} + +func TestFilterPoliciesByIDs(t *testing.T) { + tests := []struct { + name string + inputPolicies []*models.ContentUpdatePolicyV1 + requestedIDs []string + expectedPolicies []*models.ContentUpdatePolicyV1 + }{ + { + name: "all_ids_found", + inputPolicies: testPolicies, + requestedIDs: []string{"policy-001", "policy-003", "policy-005"}, + expectedPolicies: policiesByID(testPolicies, "policy-001", "policy-003", "policy-005"), + }, + { + name: "partial_ids_found", + inputPolicies: testPolicies, + requestedIDs: []string{"policy-001", "non-existent", "policy-003"}, + expectedPolicies: policiesByID(testPolicies, "policy-001", "policy-003"), + }, + { + name: "no_ids_found", + inputPolicies: testPolicies, + requestedIDs: []string{"non-existent-1", "non-existent-2"}, + expectedPolicies: []*models.ContentUpdatePolicyV1{}, + }, + { + name: "empty_id_list", + inputPolicies: testPolicies, + requestedIDs: []string{}, + expectedPolicies: []*models.ContentUpdatePolicyV1{}, + }, + { + name: "nil_policies", + inputPolicies: nil, + requestedIDs: []string{"policy-001"}, + expectedPolicies: []*models.ContentUpdatePolicyV1{}, + }, + { + name: "empty_policies", + inputPolicies: []*models.ContentUpdatePolicyV1{}, + requestedIDs: []string{"policy-001"}, + expectedPolicies: []*models.ContentUpdatePolicyV1{}, + }, + { + name: "nil_policy_in_slice", + inputPolicies: []*models.ContentUpdatePolicyV1{ + testPolicies[0], + nil, + testPolicies[1], + }, + requestedIDs: []string{"policy-001", "policy-002"}, + expectedPolicies: policiesByID(testPolicies, "policy-001", "policy-002"), + }, + { + name: "policy_with_nil_id", + inputPolicies: []*models.ContentUpdatePolicyV1{ + testPolicies[0], + { + ID: nil, + Name: utils.Addr("Policy with no ID"), + Description: utils.Addr("Test policy"), + }, + testPolicies[1], + }, + requestedIDs: []string{"policy-001", "policy-002"}, + expectedPolicies: policiesByID(testPolicies, "policy-001", "policy-002"), + }, + { + name: "single_id_match", + inputPolicies: testPolicies, + requestedIDs: []string{"policy-006"}, + expectedPolicies: policiesByID(testPolicies, "policy-006"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + filtered := contentupdatepolicy.FilterPoliciesByIDs(tt.inputPolicies, tt.requestedIDs) + assert.ElementsMatch(t, tt.expectedPolicies, filtered, "Filtered policies don't match expected policies") + }) + } +} + +func TestFilterPoliciesByAttributes(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + filters *contentupdatepolicy.ContentUpdatePoliciesDataSourceModel + inputPolicies []*models.ContentUpdatePolicyV1 + expectedPolicies []*models.ContentUpdatePolicyV1 + }{ + { + name: "name_no_matches", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + Name: types.StringValue("mac*"), + }, + inputPolicies: testPolicies, + expectedPolicies: []*models.ContentUpdatePolicyV1{}, + }, + { + name: "name_wildcard_at_start", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + Name: types.StringValue("*Policy"), + }, + inputPolicies: testPolicies, + expectedPolicies: policiesByID(testPolicies, "policy-001", "policy-004", "policy-006", "policy-007"), + }, + { + name: "name_wildcard_at_end", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + Name: types.StringValue("Test*"), + }, + inputPolicies: testPolicies, + expectedPolicies: policiesByID(testPolicies, "policy-004", "policy-005"), + }, + { + name: "name_wildcard_in_middle", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + Name: types.StringValue("Production*Desktop"), + }, + inputPolicies: testPolicies, + expectedPolicies: policiesByID(testPolicies, "policy-003"), + }, + { + name: "name_multiple_wildcards", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + Name: types.StringValue("*Production*Serv*"), + }, + inputPolicies: testPolicies, + expectedPolicies: policiesByID(testPolicies, "policy-008", "policy-009"), + }, + { + name: "description_exact_match", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + Description: types.StringValue("malware protection"), + }, + inputPolicies: testPolicies, + expectedPolicies: policiesByID(testPolicies, "policy-001"), + }, + { + name: "description_no_matches", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + Description: types.StringValue("nonexistent*"), + }, + inputPolicies: testPolicies, + expectedPolicies: []*models.ContentUpdatePolicyV1{}, + }, + { + name: "description_wildcard_at_start", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + Description: types.StringValue("*protection"), + }, + inputPolicies: testPolicies, + expectedPolicies: policiesByID(testPolicies, "policy-001", "policy-003", "policy-005", "policy-006", "policy-007", "policy-008", "policy-009"), + }, + { + name: "description_wildcard_at_end", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + Description: types.StringValue("malware*"), + }, + inputPolicies: testPolicies, + expectedPolicies: policiesByID(testPolicies, "policy-001", "policy-002", "policy-004"), + }, + { + name: "description_wildcard_in_middle", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + Description: types.StringValue("malware*protection"), + }, + inputPolicies: testPolicies, + expectedPolicies: policiesByID(testPolicies, "policy-001"), + }, + { + name: "description_multiple_wildcards", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + Description: types.StringValue("*ware*prote*"), + }, + inputPolicies: testPolicies, + expectedPolicies: policiesByID(testPolicies, "policy-001", "policy-002", "policy-005"), + }, + { + name: "created_by_exact_match", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + CreatedBy: types.StringValue("admin@example.com"), + }, + inputPolicies: testPolicies, + expectedPolicies: policiesByID(testPolicies, "policy-001", "policy-002", "policy-006", "policy-008", "policy-009", "policy-010", "policy-011"), + }, + { + name: "created_by_no_matches", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + CreatedBy: types.StringValue("nonexistent@example.com"), + }, + inputPolicies: testPolicies, + expectedPolicies: []*models.ContentUpdatePolicyV1{}, + }, + { + name: "created_by_wildcard_at_start", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + CreatedBy: types.StringValue("*@example.com"), + }, + inputPolicies: testPolicies, + expectedPolicies: policiesByID(testPolicies, "policy-001", "policy-002", "policy-003", "policy-004", "policy-006", "policy-007", "policy-008", "policy-009", "policy-010", "policy-011"), + }, + { + name: "created_by_wildcard_at_end", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + CreatedBy: types.StringValue("user@*"), + }, + inputPolicies: testPolicies, + expectedPolicies: policiesByID(testPolicies, "policy-003", "policy-004", "policy-007"), + }, + { + name: "created_by_wildcard_in_middle", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + CreatedBy: types.StringValue("admin@*example.com"), + }, + inputPolicies: testPolicies, + expectedPolicies: policiesByID(testPolicies, "policy-001", "policy-002", "policy-006", "policy-008", "policy-009", "policy-010", "policy-011"), + }, + { + name: "created_by_multiple_wildcards", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + CreatedBy: types.StringValue("*admin*example*"), + }, + inputPolicies: testPolicies, + expectedPolicies: policiesByID(testPolicies, "policy-001", "policy-002", "policy-006", "policy-008", "policy-009", "policy-010", "policy-011"), + }, + { + name: "modified_by_exact_match", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + ModifiedBy: types.StringValue("admin@example.com"), + }, + inputPolicies: testPolicies, + expectedPolicies: policiesByID(testPolicies, "policy-002", "policy-006", "policy-008", "policy-009", "policy-010", "policy-011"), + }, + { + name: "modified_by_no_matches", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + ModifiedBy: types.StringValue("nonexistent@example.com"), + }, + inputPolicies: testPolicies, + expectedPolicies: []*models.ContentUpdatePolicyV1{}, + }, + { + name: "modified_by_wildcard_at_start", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + ModifiedBy: types.StringValue("*@example.com"), + }, + inputPolicies: testPolicies, + expectedPolicies: policiesByID(testPolicies, "policy-001", "policy-002", "policy-003", "policy-004", "policy-006", "policy-007", "policy-008", "policy-009", "policy-010", "policy-011"), + }, + { + name: "modified_by_wildcard_at_end", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + ModifiedBy: types.StringValue("security@*"), + }, + inputPolicies: testPolicies, + expectedPolicies: policiesByID(testPolicies, "policy-001", "policy-003"), + }, + { + name: "modified_by_wildcard_in_middle", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + ModifiedBy: types.StringValue("security@*example.com"), + }, + inputPolicies: testPolicies, + expectedPolicies: policiesByID(testPolicies, "policy-001", "policy-003"), + }, + { + name: "modified_by_multiple_wildcards", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + ModifiedBy: types.StringValue("*admin*example*"), + }, + inputPolicies: testPolicies, + expectedPolicies: policiesByID(testPolicies, "policy-002", "policy-006", "policy-008", "policy-009", "policy-010", "policy-011"), + }, + { + name: "enabled_true", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + Enabled: types.BoolValue(true), + }, + inputPolicies: testPolicies, + expectedPolicies: policiesByID(testPolicies, "policy-001", "policy-002", "policy-003", "policy-005", "policy-006", "policy-008", "policy-010", "policy-011", "policy-012"), + }, + { + name: "enabled_false", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + Enabled: types.BoolValue(false), + }, + inputPolicies: testPolicies, + expectedPolicies: policiesByID(testPolicies, "policy-004", "policy-007", "policy-009"), + }, + { + name: "name_and_description", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + Name: types.StringValue("*Policy"), + Description: types.StringValue("*protection"), + }, + inputPolicies: testPolicies, + expectedPolicies: policiesByID(testPolicies, "policy-001", "policy-006", "policy-007"), + }, + { + name: "all_filters", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + Name: types.StringValue("*Policy"), + Description: types.StringValue("Windows protection"), + CreatedBy: types.StringValue("admin@example.com"), + ModifiedBy: types.StringValue("admin@example.com"), + Enabled: types.BoolValue(true), + }, + inputPolicies: testPolicies, + expectedPolicies: policiesByID(testPolicies, "policy-006"), + }, + { + name: "name_and_created_by", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + Name: types.StringValue("Production*"), + CreatedBy: types.StringValue("admin@*"), + }, + inputPolicies: testPolicies, + expectedPolicies: policiesByID(testPolicies, "policy-001", "policy-002", "policy-008", "policy-009"), + }, + { + name: "description_and_user_filters", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + Description: types.StringValue("*protection"), + CreatedBy: types.StringValue("admin@example.com"), + ModifiedBy: types.StringValue("admin@example.com"), + }, + inputPolicies: testPolicies, + expectedPolicies: policiesByID(testPolicies, "policy-006", "policy-008", "policy-009"), + }, + { + name: "no_filtering", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + Name: types.StringNull(), + Description: types.StringNull(), + CreatedBy: types.StringNull(), + ModifiedBy: types.StringNull(), + Enabled: types.BoolNull(), + }, + inputPolicies: testPolicies, + expectedPolicies: testPolicies, + }, + { + name: "empty_input", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{}, + inputPolicies: []*models.ContentUpdatePolicyV1{}, + expectedPolicies: []*models.ContentUpdatePolicyV1{}, + }, + { + name: "nil_input", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{}, + inputPolicies: nil, + expectedPolicies: []*models.ContentUpdatePolicyV1{}, + }, + { + name: "nil_policy_in_slice", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + Name: types.StringValue("*Policy"), + }, + inputPolicies: []*models.ContentUpdatePolicyV1{ + testPolicies[0], + nil, + testPolicies[3], + }, + expectedPolicies: policiesByID(testPolicies, "policy-001", "policy-004"), + }, + { + name: "filter_nil_name_field", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + Name: types.StringValue("*Policy"), + }, + inputPolicies: testPolicies, + expectedPolicies: policiesByID(testPolicies, "policy-001", "policy-004", "policy-006", "policy-007"), + }, + { + name: "filter_nil_description_field", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + Description: types.StringValue("*protection"), + }, + inputPolicies: testPolicies, + expectedPolicies: policiesByID(testPolicies, "policy-001", "policy-003", "policy-005", "policy-006", "policy-007", "policy-008", "policy-009"), + }, + { + name: "filter_nil_created_by_field", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + CreatedBy: types.StringValue("admin@*"), + }, + inputPolicies: testPolicies, + expectedPolicies: policiesByID(testPolicies, "policy-001", "policy-002", "policy-005", "policy-006", "policy-008", "policy-009", "policy-010", "policy-011"), + }, + { + name: "filter_nil_modified_by_field", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + ModifiedBy: types.StringValue("security@*"), + }, + inputPolicies: testPolicies, + expectedPolicies: policiesByID(testPolicies, "policy-001", "policy-003"), + }, + { + name: "filter_nil_enabled_field", + filters: &contentupdatepolicy.ContentUpdatePoliciesDataSourceModel{ + Enabled: types.BoolValue(true), + }, + inputPolicies: testPolicies, + expectedPolicies: policiesByID(testPolicies, "policy-001", "policy-002", "policy-003", "policy-005", "policy-006", "policy-008", "policy-010", "policy-011", "policy-012"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + filtered := contentupdatepolicy.FilterPoliciesByAttributes(tt.inputPolicies, tt.filters) + assert.ElementsMatch(t, tt.expectedPolicies, filtered, "Filtered policies don't match expected policies") + }) + } +} diff --git a/internal/content_update_policy/content_update_policy_precedence.go b/internal/content_update_policy/content_update_policy_precedence.go index 5bc47c41..e7e46df7 100644 --- a/internal/content_update_policy/content_update_policy_precedence.go +++ b/internal/content_update_policy/content_update_policy_precedence.go @@ -9,7 +9,6 @@ import ( "github.com/crowdstrike/gofalcon/falcon/client" "github.com/crowdstrike/gofalcon/falcon/client/content_update_policies" "github.com/crowdstrike/gofalcon/falcon/models" - "github.com/crowdstrike/terraform-provider-crowdstrike/internal/scopes" "github.com/crowdstrike/terraform-provider-crowdstrike/internal/utils" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" @@ -29,15 +28,8 @@ var ( ) var ( - precedenceDocumentationSection string = "Content Update Policy" - precedenceMarkdownDescription string = "This resource allows you to set the precedence of Content Update Policies based on the order of IDs." - precedencerequiredScopes []scopes.Scope = []scopes.Scope{ - { - Name: "Content update policies", - Read: true, - Write: true, - }, - } + precedenceDocumentationSection string = "Content Update Policy" + precedenceMarkdownDescription string = "This resource allows you to set the precedence of Content Update Policies based on the order of IDs." dynamicEnforcement = "dynamic" ) @@ -113,7 +105,7 @@ func (r *contentUpdatePolicyPrecedenceResource) Schema( resp *resource.SchemaResponse, ) { resp.Schema = schema.Schema{ - MarkdownDescription: utils.MarkdownDescription(precedenceDocumentationSection, precedenceMarkdownDescription, precedencerequiredScopes), + MarkdownDescription: utils.MarkdownDescription(precedenceDocumentationSection, precedenceMarkdownDescription, apiScopesReadWrite), Attributes: map[string]schema.Attribute{ "ids": schema.ListAttribute{ Required: true, diff --git a/internal/content_update_policy/content_update_policy_resource.go b/internal/content_update_policy/content_update_policy_resource.go index 5ab6a10d..aaebc513 100644 --- a/internal/content_update_policy/content_update_policy_resource.go +++ b/internal/content_update_policy/content_update_policy_resource.go @@ -296,15 +296,7 @@ func (r *contentPolicyResource) Schema( resp.Schema = schema.Schema{ MarkdownDescription: fmt.Sprintf( "Content Update Policy --- This resource allows management of content update policies in the CrowdStrike Falcon platform. Content update policies control how and when CrowdStrike content updates are deployed to hosts.\n\n%s", - scopes.GenerateScopeDescription( - []scopes.Scope{ - { - Name: "Content update policies", - Read: true, - Write: true, - }, - }, - ), + scopes.GenerateScopeDescription(apiScopesReadWrite), ), Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ diff --git a/internal/content_update_policy/default_content_update_policy_resource.go b/internal/content_update_policy/default_content_update_policy_resource.go index 74e28fed..09f7c862 100644 --- a/internal/content_update_policy/default_content_update_policy_resource.go +++ b/internal/content_update_policy/default_content_update_policy_resource.go @@ -230,15 +230,7 @@ func (r *defaultContentUpdatePolicyResource) Schema( resp.Schema = schema.Schema{ MarkdownDescription: fmt.Sprintf( "Content Update Policy --- This resource allows management of the default content update policy in the CrowdStrike Falcon platform. Destruction of this resource *will not* delete the default content update policy or remove any configured settings.\n\n%s", - scopes.GenerateScopeDescription( - []scopes.Scope{ - { - Name: "Content update policies", - Read: true, - Write: true, - }, - }, - ), + scopes.GenerateScopeDescription(apiScopesReadWrite), ), Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ diff --git a/internal/content_update_policy/scopes.go b/internal/content_update_policy/scopes.go new file mode 100644 index 00000000..0706fccf --- /dev/null +++ b/internal/content_update_policy/scopes.go @@ -0,0 +1,21 @@ +package contentupdatepolicy + +import ( + "github.com/crowdstrike/terraform-provider-crowdstrike/internal/scopes" +) + +var apiScopesRead = []scopes.Scope{ + { + Name: "Content Update Policy", + Read: true, + Write: false, + }, +} + +var apiScopesReadWrite = []scopes.Scope{ + { + Name: "Content Update Policy", + Read: true, + Write: true, + }, +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 046c34dc..4a450e0a 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -274,6 +274,7 @@ func (p *CrowdStrikeProvider) DataSources(ctx context.Context) []func() datasour sensorupdatepolicy.NewSensorUpdateBuildsDataSource, fcs.NewCloudAwsAccountsDataSource, contentupdatepolicy.NewContentCategoryVersionsDataSource, + contentupdatepolicy.NewContentUpdatePoliciesDataSource, cloudsecurity.NewCloudSecurityRulesDataSource, cloudcompliance.NewCloudComplianceFrameworkControlDataSource, preventionpolicy.NewPreventionPoliciesDataSource,