diff --git a/datadog/fwprovider/framework_provider.go b/datadog/fwprovider/framework_provider.go index 7d09629026..2ca302fbde 100644 --- a/datadog/fwprovider/framework_provider.go +++ b/datadog/fwprovider/framework_provider.go @@ -88,6 +88,7 @@ var Resources = []func() resource.Resource{ NewOnCallScheduleResource, NewOnCallTeamRoutingRulesResource, NewSecurityMonitoringRuleJSONResource, + NewComplianceCustomFrameworkResource, } var Datasources = []func() datasource.DataSource{ diff --git a/datadog/fwprovider/resource_datadog_compliance_custom_framework.go b/datadog/fwprovider/resource_datadog_compliance_custom_framework.go new file mode 100644 index 0000000000..282128dc0b --- /dev/null +++ b/datadog/fwprovider/resource_datadog_compliance_custom_framework.go @@ -0,0 +1,390 @@ +package fwprovider + +import ( + "context" + + "github.com/DataDog/datadog-api-client-go/v2/api/datadogV2" + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/terraform-providers/terraform-provider-datadog/datadog/internal/utils" + "github.com/terraform-providers/terraform-provider-datadog/datadog/internal/validators" +) + +var _ resource.Resource = &complianceCustomFrameworkResource{} + +type complianceCustomFrameworkResource struct { + Api *datadogV2.SecurityMonitoringApi + Auth context.Context +} + +// to handle a larger input, requirements and controls had to be lists even though order doesn't matter +// but rules can be sets since requirements and controls are lists (the performance issue happened when all were sets) +type complianceCustomFrameworkModel struct { + ID types.String `tfsdk:"id"` + Version types.String `tfsdk:"version"` + Handle types.String `tfsdk:"handle"` + Name types.String `tfsdk:"name"` + IconURL types.String `tfsdk:"icon_url"` + Requirements []complianceCustomFrameworkRequirementsModel `tfsdk:"requirements"` +} + +type complianceCustomFrameworkRequirementsModel struct { + Name types.String `tfsdk:"name"` + Controls []complianceCustomFrameworkControlsModel `tfsdk:"controls"` +} + +type complianceCustomFrameworkControlsModel struct { + Name types.String `tfsdk:"name"` + RulesID types.Set `tfsdk:"rules_id"` +} + +func NewComplianceCustomFrameworkResource() resource.Resource { + return &complianceCustomFrameworkResource{} +} + +func (r *complianceCustomFrameworkResource) Metadata(_ context.Context, _ resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "compliance_custom_framework" +} + +func (r *complianceCustomFrameworkResource) Schema(_ context.Context, _ resource.SchemaRequest, response *resource.SchemaResponse) { + response.Schema = schema.Schema{ + Description: "Provides a Datadog Compliance Custom Framework resource, which is used to create and manage compliance custom frameworks.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "The ID of the compliance custom framework resource.", + Computed: true, + }, + "version": schema.StringAttribute{ + Description: "The framework version.", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "handle": schema.StringAttribute{ + Description: "The framework handle.", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "name": schema.StringAttribute{ + Description: "The framework name.", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + Required: true, + }, + "icon_url": schema.StringAttribute{ + Description: "The URL of the icon representing the framework", + Optional: true, + }, + }, + Blocks: map[string]schema.Block{ + "requirements": schema.ListNestedBlock{ + Description: "The requirements of the framework.", + Validators: []validator.List{ + validators.DuplicateRequirementControlValidator(), + listvalidator.IsRequired(), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Description: "The name of the requirement.", + Required: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + }, + Blocks: map[string]schema.Block{ + "controls": schema.ListNestedBlock{ + Description: "The controls of the requirement.", + Validators: []validator.List{ + listvalidator.IsRequired(), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Description: "The name of the control.", + Required: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "rules_id": schema.SetAttribute{ + Description: "The set of rules IDs for the control.", + ElementType: types.StringType, + Required: true, + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + }, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func (r *complianceCustomFrameworkResource) Configure(_ context.Context, request resource.ConfigureRequest, response *resource.ConfigureResponse) { + providerData, _ := request.ProviderData.(*FrameworkProvider) + r.Api = providerData.DatadogApiInstances.GetSecurityMonitoringApiV2() + r.Auth = providerData.Auth +} + +func (r *complianceCustomFrameworkResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var state complianceCustomFrameworkModel + + diags := request.Config.Get(ctx, &state) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + _, httpResp, err := r.Api.CreateCustomFramework(r.Auth, *buildCreateFrameworkRequest(state)) + if err != nil { + if httpResp != nil && httpResp.StatusCode == 409 { // if framework already exists, try to update it with the new state + _, httpResp, getErr := r.Api.GetCustomFramework(r.Auth, state.Handle.ValueString(), state.Version.ValueString()) + // if the framework with the same handle and version does not exist, throw an error because + // only the handle matches which has to be unique + if httpResp != nil && httpResp.StatusCode == 400 { + response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "Framework with same handle already exists. Currently there is no support for two frameworks with the same handle.")) + return + } + if getErr != nil { + response.Diagnostics.Append(utils.FrameworkErrorDiag(getErr, "error getting existing compliance custom framework")) + return + } + // if the framework with the same handle and version exists, update it + _, _, updateErr := r.Api.UpdateCustomFramework(r.Auth, state.Handle.ValueString(), state.Version.ValueString(), *buildUpdateFrameworkRequest(state)) + if updateErr != nil { + response.Diagnostics.Append(utils.FrameworkErrorDiag(updateErr, "error updating existing compliance custom framework")) + return + } + } else { + response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error creating compliance custom framework")) + return + } + } + state.ID = types.StringValue(state.Handle.ValueString() + string('-') + state.Version.ValueString()) + diags = response.State.Set(ctx, &state) + response.Diagnostics.Append(diags...) +} + +func (r *complianceCustomFrameworkResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + var state complianceCustomFrameworkModel + diags := request.State.Get(ctx, &state) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + _, _, err := r.Api.DeleteCustomFramework(r.Auth, state.Handle.ValueString(), state.Version.ValueString()) + if err != nil { + response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error deleting framework")) + return + } +} + +func (r *complianceCustomFrameworkResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + var state complianceCustomFrameworkModel + diags := request.State.Get(ctx, &state) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + data, httpResp, err := r.Api.GetCustomFramework(r.Auth, state.Handle.ValueString(), state.Version.ValueString()) + // If the framework does not exist, remove it from terraform state + // This is to avoid the provider to return an error when the framework is deleted in the UI prior + if httpResp != nil && httpResp.StatusCode == 400 { + // 400 could only mean the framework does not exist + // because terraform would have already validated the framework in the create function + response.State.RemoveResource(ctx) + return + } + if err != nil { + response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error reading compliance custom framework")) + return + } + databaseState := readStateFromDatabase(data, state.Handle.ValueString(), state.Version.ValueString(), &state) + diags = response.State.Set(ctx, &databaseState) + response.Diagnostics.Append(diags...) +} + +func (r *complianceCustomFrameworkResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + var state complianceCustomFrameworkModel + diags := request.Config.Get(ctx, &state) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + state.ID = types.StringValue(state.Handle.ValueString() + "-" + state.Version.ValueString()) + + _, _, err := r.Api.UpdateCustomFramework(r.Auth, state.Handle.ValueString(), state.Version.ValueString(), *buildUpdateFrameworkRequest(state)) + if err != nil { + response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error updating compliance custom framework")) + return + } + diags = response.State.Set(ctx, &state) + response.Diagnostics.Append(diags...) +} + +func convertControlToModel(control datadogV2.CustomFrameworkControl) complianceCustomFrameworkControlsModel { + rulesID := make([]attr.Value, len(control.GetRulesId())) + for k, v := range control.GetRulesId() { + rulesID[k] = types.StringValue(v) + } + return complianceCustomFrameworkControlsModel{ + Name: types.StringValue(control.GetName()), + RulesID: types.SetValueMust(types.StringType, rulesID), + } +} + +func convertRequirementToModel(req datadogV2.CustomFrameworkRequirement) complianceCustomFrameworkRequirementsModel { + controls := make([]complianceCustomFrameworkControlsModel, len(req.GetControls())) + for j, control := range req.GetControls() { + controls[j] = convertControlToModel(control) + } + return complianceCustomFrameworkRequirementsModel{ + Name: types.StringValue(req.GetName()), + Controls: controls, + } +} + +func readStateFromDatabase(data datadogV2.GetCustomFrameworkResponse, handle string, version string, currentState *complianceCustomFrameworkModel) complianceCustomFrameworkModel { + var state complianceCustomFrameworkModel + state.ID = types.StringValue(handle + "-" + version) + state.Handle = types.StringValue(handle) + state.Version = types.StringValue(version) + state.Name = types.StringValue(data.GetData().Attributes.Name) + if data.GetData().Attributes.IconUrl != nil { + state.IconURL = types.StringValue(*data.GetData().Attributes.IconUrl) + } + + apiReqMap := make(map[string]datadogV2.CustomFrameworkRequirement) + apiControlMap := make(map[string]map[string]datadogV2.CustomFrameworkControl) + + for _, req := range data.GetData().Attributes.Requirements { + apiReqMap[req.GetName()] = req + apiControlMap[req.GetName()] = make(map[string]datadogV2.CustomFrameworkControl) + for _, control := range req.GetControls() { + apiControlMap[req.GetName()][control.GetName()] = control + } + } + + // since the requirements and controls from the API response might be in a different order than the state + // we need to sort them to match the state so terraform can detect the changes + // without taking order into account + sortedRequirements := make([]complianceCustomFrameworkRequirementsModel, 0, len(data.GetData().Attributes.Requirements)) + + if currentState != nil { + for _, currentReq := range currentState.Requirements { + currentReqName := currentReq.Name.ValueString() + if apiReq, exists := apiReqMap[currentReqName]; exists { + sortedControls := make([]complianceCustomFrameworkControlsModel, 0, len(apiReq.GetControls())) + + for _, currentControl := range currentReq.Controls { + currentControlName := currentControl.Name.ValueString() + if apiControl, exists := apiControlMap[currentReqName][currentControlName]; exists { + sortedControls = append(sortedControls, convertControlToModel(apiControl)) + delete(apiControlMap[currentReqName], currentControlName) + } + } + + for _, apiControl := range apiControlMap[currentReqName] { + sortedControls = append(sortedControls, convertControlToModel(apiControl)) + } + + sortedReq := complianceCustomFrameworkRequirementsModel{ + Name: types.StringValue(apiReq.GetName()), + Controls: sortedControls, + } + sortedRequirements = append(sortedRequirements, sortedReq) + delete(apiReqMap, currentReqName) + } + } + } + + for _, apiReq := range apiReqMap { + sortedRequirements = append(sortedRequirements, convertRequirementToModel(apiReq)) + } + + state.Requirements = sortedRequirements + return state +} + +func convertStateRequirementsToFrameworkRequirements(requirements []complianceCustomFrameworkRequirementsModel) []datadogV2.CustomFrameworkRequirement { + frameworkRequirements := make([]datadogV2.CustomFrameworkRequirement, len(requirements)) + for i, requirement := range requirements { + controls := make([]datadogV2.CustomFrameworkControl, len(requirement.Controls)) + for j, control := range requirement.Controls { + rulesID := make([]string, 0) + for _, v := range control.RulesID.Elements() { + rulesID = append(rulesID, v.(types.String).ValueString()) + } + controls[j] = *datadogV2.NewCustomFrameworkControl(control.Name.ValueString(), rulesID) + } + frameworkRequirements[i] = *datadogV2.NewCustomFrameworkRequirement(controls, requirement.Name.ValueString()) + } + return frameworkRequirements +} + +func buildCreateFrameworkRequest(state complianceCustomFrameworkModel) *datadogV2.CreateCustomFrameworkRequest { + var iconURL *string + if !state.IconURL.IsNull() && !state.IconURL.IsUnknown() { + iconURLStr := state.IconURL.ValueString() + iconURL = &iconURLStr + } + createFrameworkRequest := datadogV2.NewCreateCustomFrameworkRequestWithDefaults() + createFrameworkRequest.SetData(datadogV2.CustomFrameworkData{ + Type: "custom_framework", + Attributes: datadogV2.CustomFrameworkDataAttributes{ + Handle: state.Handle.ValueString(), + Name: state.Name.ValueString(), + IconUrl: iconURL, + Version: state.Version.ValueString(), + Requirements: convertStateRequirementsToFrameworkRequirements(state.Requirements), + }, + }) + return createFrameworkRequest +} + +func buildUpdateFrameworkRequest(state complianceCustomFrameworkModel) *datadogV2.UpdateCustomFrameworkRequest { + var iconURL *string + if !state.IconURL.IsNull() && !state.IconURL.IsUnknown() { + iconURLStr := state.IconURL.ValueString() + iconURL = &iconURLStr + } + updateFrameworkRequest := datadogV2.NewUpdateCustomFrameworkRequestWithDefaults() + updateFrameworkRequest.SetData(datadogV2.CustomFrameworkData{ + Type: "custom_framework", + Attributes: datadogV2.CustomFrameworkDataAttributes{ + Handle: state.Handle.ValueString(), + Name: state.Name.ValueString(), + Version: state.Version.ValueString(), + IconUrl: iconURL, + Requirements: convertStateRequirementsToFrameworkRequirements(state.Requirements), + }, + }) + return updateFrameworkRequest +} diff --git a/datadog/internal/validators/duplicate_requirement_control_validator.go b/datadog/internal/validators/duplicate_requirement_control_validator.go new file mode 100644 index 0000000000..89a5183f3f --- /dev/null +++ b/datadog/internal/validators/duplicate_requirement_control_validator.go @@ -0,0 +1,57 @@ +package validators + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type duplicateRequirementControlValidator struct{} + +func (v duplicateRequirementControlValidator) Description(context.Context) string { + return "checks for duplicate requirement and control names" +} + +func (v duplicateRequirementControlValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +func (v duplicateRequirementControlValidator) ValidateList(ctx context.Context, req validator.ListRequest, resp *validator.ListResponse) { + if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + return + } + + seen := make(map[string]bool) + for _, requirement := range req.ConfigValue.Elements() { + reqObj := requirement.(types.Object) + name := reqObj.Attributes()["name"].(types.String).ValueString() + if seen[name] { + resp.Diagnostics.AddError( + "Each Requirement must have a unique name", + fmt.Sprintf("Requirement name '%s' is used more than once.", name), + ) + return + } + seen[name] = true + controls := reqObj.Attributes()["controls"].(types.List) + controlNames := make(map[string]bool) + for _, control := range controls.Elements() { + ctrlObj := control.(types.Object) + ctrlName := ctrlObj.Attributes()["name"].(types.String).ValueString() + if controlNames[ctrlName] { + resp.Diagnostics.AddError( + "Each Control must have a unique name under the same requirement", + fmt.Sprintf("Control name '%s' is used more than once under requirement '%s'", ctrlName, name), + ) + return + } + controlNames[ctrlName] = true + } + } +} + +func DuplicateRequirementControlValidator() validator.List { + return &duplicateRequirementControlValidator{} +} diff --git a/datadog/tests/cassettes/TestCustomFramework_CreateAndUpdateMultipleRequirements.freeze b/datadog/tests/cassettes/TestCustomFramework_CreateAndUpdateMultipleRequirements.freeze new file mode 100644 index 0000000000..74d749e146 --- /dev/null +++ b/datadog/tests/cassettes/TestCustomFramework_CreateAndUpdateMultipleRequirements.freeze @@ -0,0 +1 @@ +2025-05-22T10:29:37.579487-05:00 \ No newline at end of file diff --git a/datadog/tests/cassettes/TestCustomFramework_CreateAndUpdateMultipleRequirements.yaml b/datadog/tests/cassettes/TestCustomFramework_CreateAndUpdateMultipleRequirements.yaml new file mode 100644 index 0000000000..94e04d8d03 --- /dev/null +++ b/datadog/tests/cassettes/TestCustomFramework_CreateAndUpdateMultipleRequirements.yaml @@ -0,0 +1,240 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 385 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"data":{"attributes":{"handle":"terraform-handle","icon_url":"test url","name":"new-name","requirements":[{"controls":[{"name":"security-control","rules_id":["def-000-be9","def-000-cea"]}],"name":"security-requirement"},{"controls":[{"name":"compliance-control","rules_id":["def-000-be9","def-000-cea"]}],"name":"compliance-requirement"}],"version":"1.0"},"type":"custom_framework"}} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 123 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 1.052087125s + - id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 412 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","icon_url":"test url","name":"new-name","requirements":[{"name":"compliance-requirement","controls":[{"name":"compliance-control","rules_id":["def-000-be9","def-000-cea"]}]},{"name":"security-requirement","controls":[{"name":"security-control","rules_id":["def-000-be9","def-000-cea"]}]}],"version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 911.326958ms + - id: 2 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 412 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","icon_url":"test url","name":"new-name","requirements":[{"name":"compliance-requirement","controls":[{"name":"compliance-control","rules_id":["def-000-be9","def-000-cea"]}]},{"name":"security-requirement","controls":[{"name":"security-control","rules_id":["def-000-be9","def-000-cea"]}]}],"version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 250.824375ms + - id: 3 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 483 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"data":{"attributes":{"handle":"terraform-handle","icon_url":"test url","name":"new-name","requirements":[{"controls":[{"name":"security-control","rules_id":["def-000-cea"]}],"name":"security-requirement"},{"controls":[{"name":"control","rules_id":["def-000-be9"]}],"name":"requirement"},{"controls":[{"name":"control-2","rules_id":["def-000-be9"]},{"name":"control-3","rules_id":["def-000-be9","def-000-cea"]}],"name":"requirement-2"}],"version":"1.0"},"type":"custom_framework"}} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: PUT + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 123 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 874.516417ms + - id: 4 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 510 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","icon_url":"test url","name":"new-name","requirements":[{"name":"requirement","controls":[{"name":"control","rules_id":["def-000-be9"]}]},{"name":"requirement-2","controls":[{"name":"control-3","rules_id":["def-000-be9","def-000-cea"]},{"name":"control-2","rules_id":["def-000-be9"]}]},{"name":"security-requirement","controls":[{"name":"security-control","rules_id":["def-000-cea"]}]}],"version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 609.843333ms + - id: 5 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: DELETE + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 180 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"description":"","handle":"terraform-handle","icon_url":"test url","name":"new-name","version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 492.356333ms + - id: 6 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 51 + uncompressed: false + body: '{"errors":[{"status":"400","title":"Bad Request"}]}' + headers: + Content-Type: + - application/vnd.api+json + status: 400 Bad Request + code: 400 + duration: 81.528292ms diff --git a/datadog/tests/cassettes/TestCustomFramework_CreateBasic.freeze b/datadog/tests/cassettes/TestCustomFramework_CreateBasic.freeze new file mode 100644 index 0000000000..44a6b5c440 --- /dev/null +++ b/datadog/tests/cassettes/TestCustomFramework_CreateBasic.freeze @@ -0,0 +1 @@ +2025-05-22T10:28:47.103907-05:00 \ No newline at end of file diff --git a/datadog/tests/cassettes/TestCustomFramework_CreateBasic.yaml b/datadog/tests/cassettes/TestCustomFramework_CreateBasic.yaml new file mode 100644 index 0000000000..4e46283fce --- /dev/null +++ b/datadog/tests/cassettes/TestCustomFramework_CreateBasic.yaml @@ -0,0 +1,342 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 252 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"data":{"attributes":{"handle":"terraform-handle","icon_url":"test-url","name":"new-framework-terraform","requirements":[{"controls":[{"name":"control1","rules_id":["def-000-be9"]}],"name":"requirement1"}],"version":"1.0"},"type":"custom_framework"}} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 123 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 496.214583ms + - id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 279 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","icon_url":"test-url","name":"new-framework-terraform","requirements":[{"name":"requirement1","controls":[{"name":"control1","rules_id":["def-000-be9"]}]}],"version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 138.789792ms + - id: 2 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 279 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","icon_url":"test-url","name":"new-framework-terraform","requirements":[{"name":"requirement1","controls":[{"name":"control1","rules_id":["def-000-be9"]}]}],"version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 216.784209ms + - id: 3 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 385 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"data":{"attributes":{"handle":"terraform-handle","icon_url":"test url","name":"new-name","requirements":[{"controls":[{"name":"security-control","rules_id":["def-000-be9","def-000-cea"]}],"name":"security-requirement"},{"controls":[{"name":"compliance-control","rules_id":["def-000-be9","def-000-cea"]}],"name":"compliance-requirement"}],"version":"1.0"},"type":"custom_framework"}} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: PUT + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 123 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 579.1545ms + - id: 4 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 412 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","icon_url":"test url","name":"new-name","requirements":[{"name":"compliance-requirement","controls":[{"name":"compliance-control","rules_id":["def-000-be9","def-000-cea"]}]},{"name":"security-requirement","controls":[{"name":"security-control","rules_id":["def-000-be9","def-000-cea"]}]}],"version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 387.4965ms + - id: 5 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 412 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","icon_url":"test url","name":"new-name","requirements":[{"name":"compliance-requirement","controls":[{"name":"compliance-control","rules_id":["def-000-be9","def-000-cea"]}]},{"name":"security-requirement","controls":[{"name":"security-control","rules_id":["def-000-be9","def-000-cea"]}]}],"version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 325.68ms + - id: 6 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 230 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"data":{"attributes":{"handle":"terraform-handle","name":"new-framework-terraform","requirements":[{"controls":[{"name":"control1","rules_id":["def-000-be9"]}],"name":"requirement1"}],"version":"1.0"},"type":"custom_framework"}} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: PUT + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 123 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 595.280625ms + - id: 7 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 257 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","name":"new-framework-terraform","requirements":[{"name":"requirement1","controls":[{"name":"control1","rules_id":["def-000-be9"]}]}],"version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 191.487459ms + - id: 8 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: DELETE + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 187 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"description":"","handle":"terraform-handle","icon_url":"","name":"new-framework-terraform","version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 416.820542ms + - id: 9 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 51 + uncompressed: false + body: '{"errors":[{"status":"400","title":"Bad Request"}]}' + headers: + Content-Type: + - application/vnd.api+json + status: 400 Bad Request + code: 400 + duration: 83.548458ms diff --git a/datadog/tests/cassettes/TestCustomFramework_CreateWithoutIconURL.freeze b/datadog/tests/cassettes/TestCustomFramework_CreateWithoutIconURL.freeze new file mode 100644 index 0000000000..bcd1668ea5 --- /dev/null +++ b/datadog/tests/cassettes/TestCustomFramework_CreateWithoutIconURL.freeze @@ -0,0 +1 @@ +2025-05-22T10:29:15.759609-05:00 \ No newline at end of file diff --git a/datadog/tests/cassettes/TestCustomFramework_CreateWithoutIconURL.yaml b/datadog/tests/cassettes/TestCustomFramework_CreateWithoutIconURL.yaml new file mode 100644 index 0000000000..8ac6513926 --- /dev/null +++ b/datadog/tests/cassettes/TestCustomFramework_CreateWithoutIconURL.yaml @@ -0,0 +1,138 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 230 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"data":{"attributes":{"handle":"terraform-handle","name":"new-framework-terraform","requirements":[{"controls":[{"name":"control1","rules_id":["def-000-be9"]}],"name":"requirement1"}],"version":"1.0"},"type":"custom_framework"}} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 123 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 506.19625ms + - id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 257 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","name":"new-framework-terraform","requirements":[{"name":"requirement1","controls":[{"name":"control1","rules_id":["def-000-be9"]}]}],"version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 276.140542ms + - id: 2 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: DELETE + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 187 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"description":"","handle":"terraform-handle","icon_url":"","name":"new-framework-terraform","version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 750.406292ms + - id: 3 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 51 + uncompressed: false + body: '{"errors":[{"status":"400","title":"Bad Request"}]}' + headers: + Content-Type: + - application/vnd.api+json + status: 400 Bad Request + code: 400 + duration: 70.235625ms diff --git a/datadog/tests/cassettes/TestCustomFramework_DeleteAfterAPIDelete.freeze b/datadog/tests/cassettes/TestCustomFramework_DeleteAfterAPIDelete.freeze new file mode 100644 index 0000000000..548c782406 --- /dev/null +++ b/datadog/tests/cassettes/TestCustomFramework_DeleteAfterAPIDelete.freeze @@ -0,0 +1 @@ +2025-05-22T11:41:02.727814-05:00 \ No newline at end of file diff --git a/datadog/tests/cassettes/TestCustomFramework_DeleteAfterAPIDelete.yaml b/datadog/tests/cassettes/TestCustomFramework_DeleteAfterAPIDelete.yaml new file mode 100644 index 0000000000..f929e5081a --- /dev/null +++ b/datadog/tests/cassettes/TestCustomFramework_DeleteAfterAPIDelete.yaml @@ -0,0 +1,273 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 252 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"data":{"attributes":{"handle":"terraform-handle","icon_url":"test-url","name":"new-framework-terraform","requirements":[{"controls":[{"name":"control1","rules_id":["def-000-be9"]}],"name":"requirement1"}],"version":"1.0"},"type":"custom_framework"}} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 123 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 519.797292ms + - id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 279 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","icon_url":"test-url","name":"new-framework-terraform","requirements":[{"name":"requirement1","controls":[{"name":"control1","rules_id":["def-000-be9"]}]}],"version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 379.494375ms + - id: 2 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: DELETE + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 195 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"description":"","handle":"terraform-handle","icon_url":"test-url","name":"new-framework-terraform","version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 338.217417ms + - id: 3 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 51 + uncompressed: false + body: '{"errors":[{"status":"400","title":"Bad Request"}]}' + headers: + Content-Type: + - application/vnd.api+json + status: 400 Bad Request + code: 400 + duration: 101.837333ms + - id: 4 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 252 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"data":{"attributes":{"handle":"terraform-handle","icon_url":"test-url","name":"new-framework-terraform","requirements":[{"controls":[{"name":"control1","rules_id":["def-000-be9"]}],"name":"requirement1"}],"version":"1.0"},"type":"custom_framework"}} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 123 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 370.99ms + - id: 5 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 279 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","icon_url":"test-url","name":"new-framework-terraform","requirements":[{"name":"requirement1","controls":[{"name":"control1","rules_id":["def-000-be9"]}]}],"version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 189.421584ms + - id: 6 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: DELETE + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 195 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"description":"","handle":"terraform-handle","icon_url":"test-url","name":"new-framework-terraform","version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 656.383917ms + - id: 7 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 51 + uncompressed: false + body: '{"errors":[{"status":"400","title":"Bad Request"}]}' + headers: + Content-Type: + - application/vnd.api+json + status: 400 Bad Request + code: 400 + duration: 99.99025ms diff --git a/datadog/tests/cassettes/TestCustomFramework_DeleteNonExistent.freeze b/datadog/tests/cassettes/TestCustomFramework_DeleteNonExistent.freeze new file mode 100644 index 0000000000..d8713a1fad --- /dev/null +++ b/datadog/tests/cassettes/TestCustomFramework_DeleteNonExistent.freeze @@ -0,0 +1 @@ +2025-04-30T17:54:47.983034-04:00 \ No newline at end of file diff --git a/datadog/tests/cassettes/TestCustomFramework_DeleteNonExistent.yaml b/datadog/tests/cassettes/TestCustomFramework_DeleteNonExistent.yaml new file mode 100644 index 0000000000..8d079d2974 --- /dev/null +++ b/datadog/tests/cassettes/TestCustomFramework_DeleteNonExistent.yaml @@ -0,0 +1,138 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 289 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"data":{"attributes":{"description":"test description","handle":"fake-handle","icon_url":"test url","name":"new-framework-terraform","requirements":[{"controls":[{"name":"control1","rules_id":["def-000-be9"]}],"name":"requirement1"}],"version":"fake-version"},"type":"custom_framework"}} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 131 + uncompressed: false + body: '{"data":{"id":"fake-handle-fake-version","type":"custom_framework","attributes":{"handle":"fake-handle","version":"fake-version"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 376.17375ms + - id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/fake-handle/fake-version + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 320 + uncompressed: false + body: '{"data":{"id":"fake-handle-fake-version","type":"custom_framework","attributes":{"description":"test description","handle":"fake-handle","icon_url":"test url","name":"new-framework-terraform","requirements":[{"name":"requirement1","controls":[{"name":"control1","rules_id":["def-000-be9"]}]}],"version":"fake-version"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 217.58925ms + - id: 2 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/fake-handle/fake-version + method: DELETE + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 219 + uncompressed: false + body: '{"data":{"id":"fake-handle-fake-version","type":"custom_framework","attributes":{"description":"test description","handle":"fake-handle","icon_url":"test url","name":"new-framework-terraform","version":"fake-version"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 417.445333ms + - id: 3 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/fake-handle/fake-version + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 51 + uncompressed: false + body: '{"errors":[{"status":"400","title":"Bad Request"}]}' + headers: + Content-Type: + - application/vnd.api+json + status: 400 Bad Request + code: 400 + duration: 41.793375ms diff --git a/datadog/tests/cassettes/TestCustomFramework_DuplicateHandle.freeze b/datadog/tests/cassettes/TestCustomFramework_DuplicateHandle.freeze new file mode 100644 index 0000000000..4ce29ffaff --- /dev/null +++ b/datadog/tests/cassettes/TestCustomFramework_DuplicateHandle.freeze @@ -0,0 +1 @@ +2025-05-26T23:32:54.142418-05:00 \ No newline at end of file diff --git a/datadog/tests/cassettes/TestCustomFramework_DuplicateHandle.yaml b/datadog/tests/cassettes/TestCustomFramework_DuplicateHandle.yaml new file mode 100644 index 0000000000..1f8a18a363 --- /dev/null +++ b/datadog/tests/cassettes/TestCustomFramework_DuplicateHandle.yaml @@ -0,0 +1,207 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 252 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"data":{"attributes":{"handle":"terraform-handle","icon_url":"test-url","name":"new-framework-terraform","requirements":[{"controls":[{"name":"control1","rules_id":["def-000-be9"]}],"name":"requirement1"}],"version":"1.0"},"type":"custom_framework"}} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 123 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 368.886833ms + - id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 279 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","icon_url":"test-url","name":"new-framework-terraform","requirements":[{"name":"requirement1","controls":[{"name":"control1","rules_id":["def-000-be9"]}]}],"version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 171.795125ms + - id: 2 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 279 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","icon_url":"test-url","name":"new-framework-terraform","requirements":[{"name":"requirement1","controls":[{"name":"control1","rules_id":["def-000-be9"]}]}],"version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 169.562916ms + - id: 3 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 266 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"data":{"attributes":{"handle":"terraform-handle","icon_url":"test-url","name":"new-framework-terraform","requirements":[{"controls":[{"name":"control1","rules_id":["def-000-be9"]}],"name":"requirement1"}],"version":"different-version"},"type":"custom_framework"}} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 127 + uncompressed: false + body: '{"errors":[{"status":"409","title":"Status Conflict","detail":"already_exists(Framework ''terraform-handle'' already existed)"}]}' + headers: + Content-Type: + - application/vnd.api+json + status: 409 Conflict + code: 409 + duration: 69.033792ms + - id: 4 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: DELETE + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 195 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"description":"","handle":"terraform-handle","icon_url":"test-url","name":"new-framework-terraform","version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 372.856ms + - id: 5 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/different-version + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 51 + uncompressed: false + body: '{"errors":[{"status":"400","title":"Bad Request"}]}' + headers: + Content-Type: + - application/vnd.api+json + status: 400 Bad Request + code: 400 + duration: 306.9705ms diff --git a/datadog/tests/cassettes/TestCustomFramework_DuplicateRuleIds.freeze b/datadog/tests/cassettes/TestCustomFramework_DuplicateRuleIds.freeze new file mode 100644 index 0000000000..dc47d9e490 --- /dev/null +++ b/datadog/tests/cassettes/TestCustomFramework_DuplicateRuleIds.freeze @@ -0,0 +1 @@ +2025-05-22T11:48:01.178393-05:00 \ No newline at end of file diff --git a/datadog/tests/cassettes/TestCustomFramework_DuplicateRuleIds.yaml b/datadog/tests/cassettes/TestCustomFramework_DuplicateRuleIds.yaml new file mode 100644 index 0000000000..9707f7b36c --- /dev/null +++ b/datadog/tests/cassettes/TestCustomFramework_DuplicateRuleIds.yaml @@ -0,0 +1,138 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 243 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"data":{"attributes":{"handle":"terraform-handle","icon_url":"test url","name":"framework-name","requirements":[{"controls":[{"name":"control1","rules_id":["def-000-be9"]}],"name":"requirement1"}],"version":"1.0"},"type":"custom_framework"}} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 123 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 646.051833ms + - id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 270 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","icon_url":"test url","name":"framework-name","requirements":[{"name":"requirement1","controls":[{"name":"control1","rules_id":["def-000-be9"]}]}],"version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 236.522542ms + - id: 2 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: DELETE + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 186 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"description":"","handle":"terraform-handle","icon_url":"test url","name":"framework-name","version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 479.3125ms + - id: 3 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 51 + uncompressed: false + body: '{"errors":[{"status":"400","title":"Bad Request"}]}' + headers: + Content-Type: + - application/vnd.api+json + status: 400 Bad Request + code: 400 + duration: 97.182917ms diff --git a/datadog/tests/cassettes/TestCustomFramework_InvalidCreate.freeze b/datadog/tests/cassettes/TestCustomFramework_InvalidCreate.freeze new file mode 100644 index 0000000000..c80ac1347c --- /dev/null +++ b/datadog/tests/cassettes/TestCustomFramework_InvalidCreate.freeze @@ -0,0 +1 @@ +2025-05-22T12:37:52.652765-05:00 \ No newline at end of file diff --git a/datadog/tests/cassettes/TestCustomFramework_InvalidCreate.yaml b/datadog/tests/cassettes/TestCustomFramework_InvalidCreate.yaml new file mode 100644 index 0000000000..92fb6030eb --- /dev/null +++ b/datadog/tests/cassettes/TestCustomFramework_InvalidCreate.yaml @@ -0,0 +1,39 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 256 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"data":{"attributes":{"handle":"terraform-handle","icon_url":"test url","name":"new-framework-terraform","requirements":[{"controls":[{"name":"control1","rules_id":["invalid-rule-id"]}],"name":"requirement1"}],"version":"1.0"},"type":"custom_framework"}} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 116 + uncompressed: false + body: '{"errors":[{"status":"400","title":"Bad Request","detail":"invalid_argument(Cannot find rule id invalid-rule-id)"}]}' + headers: + Content-Type: + - application/vnd.api+json + status: 400 Bad Request + code: 400 + duration: 364.630958ms diff --git a/datadog/tests/cassettes/TestCustomFramework_RecreateAfterAPIDelete.freeze b/datadog/tests/cassettes/TestCustomFramework_RecreateAfterAPIDelete.freeze new file mode 100644 index 0000000000..fed2f6f156 --- /dev/null +++ b/datadog/tests/cassettes/TestCustomFramework_RecreateAfterAPIDelete.freeze @@ -0,0 +1 @@ +2025-05-22T11:40:36.279056-05:00 \ No newline at end of file diff --git a/datadog/tests/cassettes/TestCustomFramework_RecreateAfterAPIDelete.yaml b/datadog/tests/cassettes/TestCustomFramework_RecreateAfterAPIDelete.yaml new file mode 100644 index 0000000000..80c310b837 --- /dev/null +++ b/datadog/tests/cassettes/TestCustomFramework_RecreateAfterAPIDelete.yaml @@ -0,0 +1,273 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 252 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"data":{"attributes":{"handle":"terraform-handle","icon_url":"test-url","name":"new-framework-terraform","requirements":[{"controls":[{"name":"control1","rules_id":["def-000-be9"]}],"name":"requirement1"}],"version":"1.0"},"type":"custom_framework"}} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 123 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 656.768083ms + - id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 279 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","icon_url":"test-url","name":"new-framework-terraform","requirements":[{"name":"requirement1","controls":[{"name":"control1","rules_id":["def-000-be9"]}]}],"version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 180.020208ms + - id: 2 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: DELETE + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 195 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"description":"","handle":"terraform-handle","icon_url":"test-url","name":"new-framework-terraform","version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 536.796417ms + - id: 3 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 51 + uncompressed: false + body: '{"errors":[{"status":"400","title":"Bad Request"}]}' + headers: + Content-Type: + - application/vnd.api+json + status: 400 Bad Request + code: 400 + duration: 80.27375ms + - id: 4 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 252 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"data":{"attributes":{"handle":"terraform-handle","icon_url":"test-url","name":"new-framework-terraform","requirements":[{"controls":[{"name":"control1","rules_id":["def-000-be9"]}],"name":"requirement1"}],"version":"1.0"},"type":"custom_framework"}} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 123 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 517.871542ms + - id: 5 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 279 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","icon_url":"test-url","name":"new-framework-terraform","requirements":[{"name":"requirement1","controls":[{"name":"control1","rules_id":["def-000-be9"]}]}],"version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 153.309084ms + - id: 6 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: DELETE + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 195 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"description":"","handle":"terraform-handle","icon_url":"test-url","name":"new-framework-terraform","version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 346.506375ms + - id: 7 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 51 + uncompressed: false + body: '{"errors":[{"status":"400","title":"Bad Request"}]}' + headers: + Content-Type: + - application/vnd.api+json + status: 400 Bad Request + code: 400 + duration: 93.734042ms diff --git a/datadog/tests/cassettes/TestCustomFramework_RecreateOnImmutableFields.freeze b/datadog/tests/cassettes/TestCustomFramework_RecreateOnImmutableFields.freeze new file mode 100644 index 0000000000..fa34135a73 --- /dev/null +++ b/datadog/tests/cassettes/TestCustomFramework_RecreateOnImmutableFields.freeze @@ -0,0 +1 @@ +2025-05-22T11:43:27.403873-05:00 \ No newline at end of file diff --git a/datadog/tests/cassettes/TestCustomFramework_RecreateOnImmutableFields.yaml b/datadog/tests/cassettes/TestCustomFramework_RecreateOnImmutableFields.yaml new file mode 100644 index 0000000000..255c5c20ad --- /dev/null +++ b/datadog/tests/cassettes/TestCustomFramework_RecreateOnImmutableFields.yaml @@ -0,0 +1,306 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 252 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"data":{"attributes":{"handle":"terraform-handle","icon_url":"test-url","name":"new-framework-terraform","requirements":[{"controls":[{"name":"control1","rules_id":["def-000-be9"]}],"name":"requirement1"}],"version":"1.0"},"type":"custom_framework"}} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 123 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 417.890125ms + - id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 279 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","icon_url":"test-url","name":"new-framework-terraform","requirements":[{"name":"requirement1","controls":[{"name":"control1","rules_id":["def-000-be9"]}]}],"version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 356.255ms + - id: 2 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 279 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","icon_url":"test-url","name":"new-framework-terraform","requirements":[{"name":"requirement1","controls":[{"name":"control1","rules_id":["def-000-be9"]}]}],"version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 177.291709ms + - id: 3 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: DELETE + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 195 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"description":"","handle":"terraform-handle","icon_url":"test-url","name":"new-framework-terraform","version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 439.09975ms + - id: 4 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 256 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"data":{"attributes":{"handle":"terraform-handle-new","icon_url":"test-url","name":"new-framework-terraform","requirements":[{"controls":[{"name":"control1","rules_id":["def-000-be9"]}],"name":"requirement1"}],"version":"2.0"},"type":"custom_framework"}} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 131 + uncompressed: false + body: '{"data":{"id":"terraform-handle-new-2.0","type":"custom_framework","attributes":{"handle":"terraform-handle-new","version":"2.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 430.19675ms + - id: 5 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 51 + uncompressed: false + body: '{"errors":[{"status":"400","title":"Bad Request"}]}' + headers: + Content-Type: + - application/vnd.api+json + status: 400 Bad Request + code: 400 + duration: 100.05225ms + - id: 6 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle-new/2.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 287 + uncompressed: false + body: '{"data":{"id":"terraform-handle-new-2.0","type":"custom_framework","attributes":{"handle":"terraform-handle-new","icon_url":"test-url","name":"new-framework-terraform","requirements":[{"name":"requirement1","controls":[{"name":"control1","rules_id":["def-000-be9"]}]}],"version":"2.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 168.000083ms + - id: 7 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle-new/2.0 + method: DELETE + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 203 + uncompressed: false + body: '{"data":{"id":"terraform-handle-new-2.0","type":"custom_framework","attributes":{"description":"","handle":"terraform-handle-new","icon_url":"test-url","name":"new-framework-terraform","version":"2.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 336.826417ms + - id: 8 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle-new/2.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 51 + uncompressed: false + body: '{"errors":[{"status":"400","title":"Bad Request"}]}' + headers: + Content-Type: + - application/vnd.api+json + status: 400 Bad Request + code: 400 + duration: 84.338167ms diff --git a/datadog/tests/cassettes/TestCustomFramework_SameFrameworkID.freeze b/datadog/tests/cassettes/TestCustomFramework_SameFrameworkID.freeze new file mode 100644 index 0000000000..717d43f5ba --- /dev/null +++ b/datadog/tests/cassettes/TestCustomFramework_SameFrameworkID.freeze @@ -0,0 +1 @@ +2025-05-22T10:30:04.353302-05:00 \ No newline at end of file diff --git a/datadog/tests/cassettes/TestCustomFramework_SameFrameworkID.yaml b/datadog/tests/cassettes/TestCustomFramework_SameFrameworkID.yaml new file mode 100644 index 0000000000..73889a4c7c --- /dev/null +++ b/datadog/tests/cassettes/TestCustomFramework_SameFrameworkID.yaml @@ -0,0 +1,408 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 259 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"data":{"attributes":{"handle":"new-terraform","icon_url":"test-url","name":"new-framework-terraform","requirements":[{"controls":[{"name":"control1","rules_id":["def-000-be9"]}],"name":"requirement1"}],"version":"framework-1.0"},"type":"custom_framework"}} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 137 + uncompressed: false + body: '{"data":{"id":"new-terraform-framework-1.0","type":"custom_framework","attributes":{"handle":"new-terraform","version":"framework-1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 620.897666ms + - id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/new-terraform/framework-1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 293 + uncompressed: false + body: '{"data":{"id":"new-terraform-framework-1.0","type":"custom_framework","attributes":{"handle":"new-terraform","icon_url":"test-url","name":"new-framework-terraform","requirements":[{"name":"requirement1","controls":[{"name":"control1","rules_id":["def-000-be9"]}]}],"version":"framework-1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 173.264625ms + - id: 2 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/new-terraform/framework-1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 293 + uncompressed: false + body: '{"data":{"id":"new-terraform-framework-1.0","type":"custom_framework","attributes":{"handle":"new-terraform","icon_url":"test-url","name":"new-framework-terraform","requirements":[{"name":"requirement1","controls":[{"name":"control1","rules_id":["def-000-be9"]}]}],"version":"framework-1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 242.677875ms + - id: 3 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/new-terraform/framework-1.0 + method: DELETE + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 209 + uncompressed: false + body: '{"data":{"id":"new-terraform-framework-1.0","type":"custom_framework","attributes":{"description":"","handle":"new-terraform","icon_url":"test-url","name":"new-framework-terraform","version":"framework-1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 343.581417ms + - id: 4 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 259 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"data":{"attributes":{"handle":"new","icon_url":"test-url","name":"new-framework-terraform","requirements":[{"controls":[{"name":"control1","rules_id":["def-000-be9"]}],"name":"requirement1"}],"version":"terraform-framework-1.0"},"type":"custom_framework"}} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 137 + uncompressed: false + body: '{"data":{"id":"new-terraform-framework-1.0","type":"custom_framework","attributes":{"handle":"new","version":"terraform-framework-1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 525.991166ms + - id: 5 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/new/terraform-framework-1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 293 + uncompressed: false + body: '{"data":{"id":"new-terraform-framework-1.0","type":"custom_framework","attributes":{"handle":"new","icon_url":"test-url","name":"new-framework-terraform","requirements":[{"name":"requirement1","controls":[{"name":"control1","rules_id":["def-000-be9"]}]}],"version":"terraform-framework-1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 214.634792ms + - id: 6 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/new/terraform-framework-1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 293 + uncompressed: false + body: '{"data":{"id":"new-terraform-framework-1.0","type":"custom_framework","attributes":{"handle":"new","icon_url":"test-url","name":"new-framework-terraform","requirements":[{"name":"requirement1","controls":[{"name":"control1","rules_id":["def-000-be9"]}]}],"version":"terraform-framework-1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 200.667916ms + - id: 7 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/new/terraform-framework-1.0 + method: DELETE + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 209 + uncompressed: false + body: '{"data":{"id":"new-terraform-framework-1.0","type":"custom_framework","attributes":{"description":"","handle":"new","icon_url":"test-url","name":"new-framework-terraform","version":"terraform-framework-1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 331.03ms + - id: 8 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 259 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"data":{"attributes":{"handle":"new-terraform","icon_url":"test-url","name":"new-framework-terraform","requirements":[{"controls":[{"name":"control1","rules_id":["def-000-be9"]}],"name":"requirement1"}],"version":"framework-1.0"},"type":"custom_framework"}} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 137 + uncompressed: false + body: '{"data":{"id":"new-terraform-framework-1.0","type":"custom_framework","attributes":{"handle":"new-terraform","version":"framework-1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 347.11125ms + - id: 9 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/new-terraform/framework-1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 293 + uncompressed: false + body: '{"data":{"id":"new-terraform-framework-1.0","type":"custom_framework","attributes":{"handle":"new-terraform","icon_url":"test-url","name":"new-framework-terraform","requirements":[{"name":"requirement1","controls":[{"name":"control1","rules_id":["def-000-be9"]}]}],"version":"framework-1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 231.189833ms + - id: 10 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/new-terraform/framework-1.0 + method: DELETE + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 209 + uncompressed: false + body: '{"data":{"id":"new-terraform-framework-1.0","type":"custom_framework","attributes":{"description":"","handle":"new-terraform","icon_url":"test-url","name":"new-framework-terraform","version":"framework-1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 362.871458ms + - id: 11 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/new-terraform/framework-1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 51 + uncompressed: false + body: '{"errors":[{"status":"400","title":"Bad Request"}]}' + headers: + Content-Type: + - application/vnd.api+json + status: 400 Bad Request + code: 400 + duration: 69.891458ms diff --git a/datadog/tests/cassettes/TestCustomFramework_TestImportingFramework.freeze b/datadog/tests/cassettes/TestCustomFramework_TestImportingFramework.freeze new file mode 100644 index 0000000000..505bd65163 --- /dev/null +++ b/datadog/tests/cassettes/TestCustomFramework_TestImportingFramework.freeze @@ -0,0 +1 @@ +2025-05-01T15:59:42.287109-04:00 \ No newline at end of file diff --git a/datadog/tests/cassettes/TestCustomFramework_TestImportingFramework.yaml b/datadog/tests/cassettes/TestCustomFramework_TestImportingFramework.yaml new file mode 100644 index 0000000000..53a7d252e3 --- /dev/null +++ b/datadog/tests/cassettes/TestCustomFramework_TestImportingFramework.yaml @@ -0,0 +1,69 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/new-terraform-framework-test/2 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 313 + uncompressed: false + body: '{"data":{"id":"new-terraform-framework-test-2","type":"custom_framework","attributes":{"description":"","handle":"new-terraform-framework-test","icon_url":"","name":"new-terraform-framework-test","requirements":[{"name":"requirement1","controls":[{"name":"control1","rules_id":["def-000-u48"]}]}],"version":"2"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 245.226916ms + - id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/new-terraform-framework-test/2 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 313 + uncompressed: false + body: '{"data":{"id":"new-terraform-framework-test-2","type":"custom_framework","attributes":{"description":"","handle":"new-terraform-framework-test","icon_url":"","name":"new-terraform-framework-test","requirements":[{"name":"requirement1","controls":[{"name":"control1","rules_id":["def-000-u48"]}]}],"version":"2"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 159.962333ms diff --git a/datadog/tests/cassettes/TestCustomFramework_UpdateIfFrameworkExists.freeze b/datadog/tests/cassettes/TestCustomFramework_UpdateIfFrameworkExists.freeze new file mode 100644 index 0000000000..1e94fe1bad --- /dev/null +++ b/datadog/tests/cassettes/TestCustomFramework_UpdateIfFrameworkExists.freeze @@ -0,0 +1 @@ +2025-05-27T08:37:45.083914-05:00 \ No newline at end of file diff --git a/datadog/tests/cassettes/TestCustomFramework_UpdateIfFrameworkExists.yaml b/datadog/tests/cassettes/TestCustomFramework_UpdateIfFrameworkExists.yaml new file mode 100644 index 0000000000..3411325ac1 --- /dev/null +++ b/datadog/tests/cassettes/TestCustomFramework_UpdateIfFrameworkExists.yaml @@ -0,0 +1,243 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 255 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"data":{"attributes":{"handle":"terraform-handle","icon_url":"","name":"existing-framework","requirements":[{"controls":[{"name":"existing-control","rules_id":["def-000-be9"]}],"name":"existing-requirement"}],"version":"1.0"},"type":"custom_framework"}} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 123 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 262.048625ms + - id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 252 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"data":{"attributes":{"handle":"terraform-handle","icon_url":"test-url","name":"new-framework-terraform","requirements":[{"controls":[{"name":"control1","rules_id":["def-000-be9"]}],"name":"requirement1"}],"version":"1.0"},"type":"custom_framework"}} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 127 + uncompressed: false + body: '{"errors":[{"status":"409","title":"Status Conflict","detail":"already_exists(Framework ''terraform-handle'' already existed)"}]}' + headers: + Content-Type: + - application/vnd.api+json + status: 409 Conflict + code: 409 + duration: 63.423875ms + - id: 2 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 268 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","name":"existing-framework","requirements":[{"name":"existing-requirement","controls":[{"name":"existing-control","rules_id":["def-000-be9"]}]}],"version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 165.03725ms + - id: 3 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 252 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"data":{"attributes":{"handle":"terraform-handle","icon_url":"test-url","name":"new-framework-terraform","requirements":[{"controls":[{"name":"control1","rules_id":["def-000-be9"]}],"name":"requirement1"}],"version":"1.0"},"type":"custom_framework"}} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: PUT + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 123 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 394.019625ms + - id: 4 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 279 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"handle":"terraform-handle","icon_url":"test-url","name":"new-framework-terraform","requirements":[{"name":"requirement1","controls":[{"name":"control1","rules_id":["def-000-be9"]}]}],"version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 171.630875ms + - id: 5 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: DELETE + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 195 + uncompressed: false + body: '{"data":{"id":"terraform-handle-1.0","type":"custom_framework","attributes":{"description":"","handle":"terraform-handle","icon_url":"test-url","name":"new-framework-terraform","version":"1.0"}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 377.870958ms + - id: 6 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/cloud_security_management/custom_frameworks/terraform-handle/1.0 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 51 + uncompressed: false + body: '{"errors":[{"status":"400","title":"Bad Request"}]}' + headers: + Content-Type: + - application/vnd.api+json + status: 400 Bad Request + code: 400 + duration: 61.166708ms diff --git a/datadog/tests/provider_test.go b/datadog/tests/provider_test.go index 51b5af5bb3..fd45e2903f 100644 --- a/datadog/tests/provider_test.go +++ b/datadog/tests/provider_test.go @@ -270,6 +270,7 @@ var testFiles2EndpointTags = map[string]string{ "tests/resource_datadog_webhook_custom_variable_test": "webhook_custom_variable", "tests/resource_datadog_webhook_test": "webhook", "tests/resource_datadog_workflow_automation_test": "workflow_automation", + "tests/resource_datadog_compliance_custom_framework_test": "compliance_custom_framework", } // getEndpointTagValue traverses callstack frames to find the test function that invoked this call; diff --git a/datadog/tests/resource_datadog_compliance_custom_framework_test.go b/datadog/tests/resource_datadog_compliance_custom_framework_test.go new file mode 100644 index 0000000000..4533fe040b --- /dev/null +++ b/datadog/tests/resource_datadog_compliance_custom_framework_test.go @@ -0,0 +1,937 @@ +package test + +import ( + "context" + "fmt" + "regexp" + "testing" + + "github.com/DataDog/datadog-api-client-go/v2/api/datadogV2" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + + "github.com/terraform-providers/terraform-provider-datadog/datadog/fwprovider" +) + +type complianceCustomFrameworkModel struct { + Handle types.String + Version types.String + Name types.String + IconURL types.String + Requirements []complianceCustomFrameworkRequirementsModel +} + +type complianceCustomFrameworkRequirementsModel struct { + Name types.String + Controls []complianceCustomFrameworkControlsModel +} + +type complianceCustomFrameworkControlsModel struct { + Name types.String + RulesID []string +} + +func buildCreateFrameworkRequest(model complianceCustomFrameworkModel) *datadogV2.CreateCustomFrameworkRequest { + req := datadogV2.NewCreateCustomFrameworkRequestWithDefaults() + iconURL := model.IconURL.ValueString() + req.SetData(datadogV2.CustomFrameworkData{ + Type: "custom_framework", + Attributes: datadogV2.CustomFrameworkDataAttributes{ + Handle: model.Handle.ValueString(), + Name: model.Name.ValueString(), + IconUrl: &iconURL, + Version: model.Version.ValueString(), + Requirements: func() []datadogV2.CustomFrameworkRequirement { + requirements := make([]datadogV2.CustomFrameworkRequirement, len(model.Requirements)) + for i, req := range model.Requirements { + controls := make([]datadogV2.CustomFrameworkControl, len(req.Controls)) + for j, ctrl := range req.Controls { + controls[j] = *datadogV2.NewCustomFrameworkControl(ctrl.Name.ValueString(), ctrl.RulesID) + } + requirements[i] = *datadogV2.NewCustomFrameworkRequirement(controls, req.Name.ValueString()) + } + return requirements + }(), + }, + }) + return req +} + +func TestCustomFramework_CreateBasic(t *testing.T) { + handle := "terraform-handle" + version := "1.0" + + ctx, providers, accProviders := testAccFrameworkMuxProviders(context.Background(), t) + path := "datadog_compliance_custom_framework.sample_rules" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: accProviders, + CheckDestroy: testAccCheckDatadogFrameworkDestroy(ctx, providers.frameworkProvider, path, version, handle), + Steps: []resource.TestStep{ + { + Config: testAccCheckDatadogCreateFramework(version, handle), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(path, "handle", handle), + resource.TestCheckResourceAttr(path, "version", version), + resource.TestCheckResourceAttr(path, "name", "new-framework-terraform"), + resource.TestCheckResourceAttr(path, "icon_url", "test-url"), + resource.TestCheckResourceAttr(path, "requirements.0.name", "requirement1"), + resource.TestCheckResourceAttr(path, "requirements.0.controls.0.name", "control1"), + resource.TestCheckResourceAttr(path, "requirements.0.controls.0.rules_id.0", "def-000-be9")), + }, + { + Config: testAccCheckDatadogCreateFrameworkWithMultipleRequirements(version, handle), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(path, "handle", handle), + resource.TestCheckResourceAttr(path, "version", version), + resource.TestCheckResourceAttr(path, "requirements.0.name", "security-requirement"), + resource.TestCheckResourceAttr(path, "requirements.1.name", "compliance-requirement"), + resource.TestCheckResourceAttr(path, "requirements.0.controls.0.name", "security-control"), + resource.TestCheckResourceAttr(path, "requirements.1.controls.0.name", "compliance-control"), + resource.TestCheckTypeSetElemAttr(path, "requirements.0.controls.0.rules_id.*", "def-000-be9"), + resource.TestCheckTypeSetElemAttr(path, "requirements.0.controls.0.rules_id.*", "def-000-cea"), + resource.TestCheckTypeSetElemAttr(path, "requirements.1.controls.0.rules_id.*", "def-000-be9"), + resource.TestCheckTypeSetElemAttr(path, "requirements.1.controls.0.rules_id.*", "def-000-cea"), + ), + }, + { + Config: testAccCheckDatadogCreateFrameworkWithoutOptionalFields(version, handle), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(path, "handle", handle), + resource.TestCheckResourceAttr(path, "version", version), + resource.TestCheckResourceAttr(path, "name", "new-framework-terraform"), + resource.TestCheckResourceAttr(path, "requirements.0.name", "requirement1"), + resource.TestCheckResourceAttr(path, "requirements.0.controls.0.name", "control1"), + resource.TestCheckResourceAttr(path, "requirements.0.controls.0.rules_id.0", "def-000-be9"), + ), + }, + }, + }) +} + +func TestCustomFramework_CreateWithoutIconURL(t *testing.T) { + handle := "terraform-handle" + version := "1.0" + + ctx, providers, accProviders := testAccFrameworkMuxProviders(context.Background(), t) + path := "datadog_compliance_custom_framework.sample_rules" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: accProviders, + CheckDestroy: testAccCheckDatadogFrameworkDestroy(ctx, providers.frameworkProvider, path, version, handle), + Steps: []resource.TestStep{ + { + Config: testAccCheckDatadogCreateFrameworkWithoutOptionalFields(version, handle), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(path, "handle", handle), + resource.TestCheckResourceAttr(path, "version", version), + resource.TestCheckResourceAttr(path, "name", "new-framework-terraform"), + resource.TestCheckResourceAttr(path, "requirements.0.name", "requirement1"), + resource.TestCheckResourceAttr(path, "requirements.0.controls.0.name", "control1"), + resource.TestCheckResourceAttr(path, "requirements.0.controls.0.rules_id.0", "def-000-be9"), + ), + }, + }, + }) +} + +func TestCustomFramework_CreateAndUpdateMultipleRequirements(t *testing.T) { + handle := "terraform-handle" + version := "1.0" + + ctx, providers, accProviders := testAccFrameworkMuxProviders(context.Background(), t) + path := "datadog_compliance_custom_framework.sample_rules" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: accProviders, + CheckDestroy: testAccCheckDatadogFrameworkDestroy(ctx, providers.frameworkProvider, path, version, handle), + Steps: []resource.TestStep{ + { + Config: testAccCheckDatadogCreateFrameworkWithMultipleRequirements(version, handle), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(path, "handle", handle), + resource.TestCheckResourceAttr(path, "version", version), + resource.TestCheckResourceAttr(path, "requirements.0.name", "security-requirement"), + resource.TestCheckResourceAttr(path, "requirements.1.name", "compliance-requirement"), + resource.TestCheckResourceAttr(path, "requirements.0.controls.0.name", "security-control"), + resource.TestCheckResourceAttr(path, "requirements.1.controls.0.name", "compliance-control"), + resource.TestCheckTypeSetElemAttr(path, "requirements.0.controls.0.rules_id.*", "def-000-be9"), + resource.TestCheckTypeSetElemAttr(path, "requirements.0.controls.0.rules_id.*", "def-000-cea"), + resource.TestCheckTypeSetElemAttr(path, "requirements.1.controls.0.rules_id.*", "def-000-be9"), + resource.TestCheckTypeSetElemAttr(path, "requirements.1.controls.0.rules_id.*", "def-000-cea"), + ), + }, + { + Config: testAccCheckDatadogUpdateFrameworkWithMultipleRequirements(version, handle), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(path, "handle", handle), + resource.TestCheckResourceAttr(path, "version", version), + resource.TestCheckResourceAttr(path, "requirements.0.name", "security-requirement"), + resource.TestCheckResourceAttr(path, "requirements.1.name", "requirement"), + resource.TestCheckResourceAttr(path, "requirements.2.name", "requirement-2"), + resource.TestCheckResourceAttr(path, "requirements.0.controls.0.name", "security-control"), + resource.TestCheckResourceAttr(path, "requirements.1.controls.0.name", "control"), + resource.TestCheckResourceAttr(path, "requirements.2.controls.0.name", "control-2"), + resource.TestCheckResourceAttr(path, "requirements.2.controls.1.name", "control-3"), + resource.TestCheckTypeSetElemAttr(path, "requirements.0.controls.0.rules_id.*", "def-000-cea"), + resource.TestCheckTypeSetElemAttr(path, "requirements.1.controls.0.rules_id.*", "def-000-be9"), + resource.TestCheckTypeSetElemAttr(path, "requirements.2.controls.0.rules_id.*", "def-000-be9"), + resource.TestCheckTypeSetElemAttr(path, "requirements.2.controls.1.rules_id.*", "def-000-cea"), + resource.TestCheckTypeSetElemAttr(path, "requirements.2.controls.1.rules_id.*", "def-000-be9"), + ), + }, + }, + }) +} + +// these handle and version combinations would result in the same state ID however +// terraform would still be able to tell the framework states apart since they have unique resource names (which is required by terraform) +func TestCustomFramework_SameFrameworkID(t *testing.T) { + handle := "new-terraform" + version := "framework-1.0" + + handle2 := "new" + version2 := "terraform-framework-1.0" + + ctx, providers, accProviders := testAccFrameworkMuxProviders(context.Background(), t) + path := "datadog_compliance_custom_framework.sample_rules" + path2 := "datadog_compliance_custom_framework.sample_framework" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: accProviders, + CheckDestroy: testAccCheckDatadogFrameworkDestroy(ctx, providers.frameworkProvider, path, version, handle), + Steps: []resource.TestStep{ + { + Config: testAccCheckDatadogCreateFramework(version, handle), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(path, "handle", handle), + resource.TestCheckResourceAttr(path, "version", version), + ), + }, + { + Config: testAccCheckDatadogCreateSecondFramework(version2, handle2), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(path2, "handle", handle2), + resource.TestCheckResourceAttr(path2, "version", version2), + ), + }, + { + Config: testAccCheckDatadogCreateFramework(version, handle), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(path, "handle", handle), + resource.TestCheckResourceAttr(path, "version", version), + ), + ExpectNonEmptyPlan: false, + }, + }, + }) +} + +func TestCustomFramework_InvalidCreate(t *testing.T) { + handle := "terraform-handle" + version := "1.0" + + ctx, providers, accProviders := testAccFrameworkMuxProviders(context.Background(), t) + path := "datadog_compliance_custom_framework.sample_rules" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: accProviders, + CheckDestroy: testAccCheckDatadogFrameworkDestroy(ctx, providers.frameworkProvider, path, version, handle), + Steps: []resource.TestStep{ + { + Config: testAccCheckDatadogCreateInvalidFrameworkName(version, handle), + ExpectError: regexp.MustCompile("Invalid Attribute Value Length"), + }, + { + Config: testAccCheckDatadogCreateFrameworkRuleIdsInvalid(version, handle), + ExpectError: regexp.MustCompile("400 Bad Request"), + }, + { + Config: testAccCheckDatadogCreateFrameworkNoRequirements(version, handle), + ExpectError: regexp.MustCompile("Invalid Block"), + }, + { + Config: testAccCheckDatadogCreateFrameworkWithNoControls(version, handle), + ExpectError: regexp.MustCompile("Invalid Block"), + }, + { + Config: testAccCheckDatadogCreateEmptyHandle(version), + ExpectError: regexp.MustCompile("Invalid Attribute Value Length"), + }, + { + Config: testAccCheckDatadogCreateEmptyVersion(handle), + ExpectError: regexp.MustCompile("Invalid Attribute Value Length"), + }, + { + Config: testAccCheckDatadogDuplicateRequirementName(version, handle), + ExpectError: regexp.MustCompile(".*Each Requirement must have a unique name.*"), + }, + { + Config: testAccCheckDatadogDuplicateControlName(version, handle), + ExpectError: regexp.MustCompile(".*Each Control must have a unique name under the same requirement.*"), + }, + { + Config: testAccCheckDatadogDuplicateRequirement(version, handle), + ExpectError: regexp.MustCompile(".*Each Requirement must have a unique name.*"), + }, + { + Config: testAccCheckDatadogDuplicateControl(version, handle), + ExpectError: regexp.MustCompile(".*Each Control must have a unique name under the same requirement.*"), + }, + { + Config: testAccCheckDatadogEmptyRequirementName(version, handle), + ExpectError: regexp.MustCompile("Invalid Attribute Value Length"), + }, + { + Config: testAccCheckDatadogEmptyControlName(version, handle), + ExpectError: regexp.MustCompile("Invalid Attribute Value Length"), + }, + { + Config: testAccCheckDatadogCreateFrameworkWithNoRulesId(version, handle), + ExpectError: regexp.MustCompile("Invalid Attribute Value"), + }, + }, + }) +} + +func TestCustomFramework_RecreateAfterAPIDelete(t *testing.T) { + handle := "terraform-handle" + version := "1.0" + + ctx, providers, accProviders := testAccFrameworkMuxProviders(context.Background(), t) + path := "datadog_compliance_custom_framework.sample_rules" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: accProviders, + CheckDestroy: testAccCheckDatadogFrameworkDestroy(ctx, providers.frameworkProvider, path, version, handle), + Steps: []resource.TestStep{ + { + Config: testAccCheckDatadogCreateFramework(version, handle), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(path, "handle", handle), + resource.TestCheckResourceAttr(path, "version", version), + resource.TestCheckResourceAttr(path, "requirements.0.name", "requirement1"), + resource.TestCheckResourceAttr(path, "requirements.0.controls.0.name", "control1"), + resource.TestCheckResourceAttr(path, "requirements.0.controls.0.rules_id.0", "def-000-be9"), + ), + }, + { + PreConfig: func() { + // Delete the framework directly via API + _, _, err := providers.frameworkProvider.DatadogApiInstances.GetSecurityMonitoringApiV2().DeleteCustomFramework(providers.frameworkProvider.Auth, handle, version) + if err != nil { + t.Fatalf("Failed to delete framework: %v", err) + } + }, + Config: testAccCheckDatadogCreateFramework(version, handle), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(path, "handle", handle), + resource.TestCheckResourceAttr(path, "version", version), + resource.TestCheckResourceAttr(path, "requirements.0.name", "requirement1"), + resource.TestCheckResourceAttr(path, "requirements.0.controls.0.name", "control1"), + resource.TestCheckResourceAttr(path, "requirements.0.controls.0.rules_id.0", "def-000-be9"), + ), + }, + }, + }) +} + +func TestCustomFramework_DeleteAfterAPIDelete(t *testing.T) { + handle := "terraform-handle" + version := "1.0" + + ctx, providers, accProviders := testAccFrameworkMuxProviders(context.Background(), t) + path := "datadog_compliance_custom_framework.sample_rules" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: accProviders, + CheckDestroy: testAccCheckDatadogFrameworkDestroy(ctx, providers.frameworkProvider, path, version, handle), + Steps: []resource.TestStep{ + { + Config: testAccCheckDatadogCreateFramework(version, handle), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(path, "handle", handle), + resource.TestCheckResourceAttr(path, "version", version), + resource.TestCheckResourceAttr(path, "requirements.0.name", "requirement1"), + resource.TestCheckResourceAttr(path, "requirements.0.controls.0.name", "control1"), + resource.TestCheckResourceAttr(path, "requirements.0.controls.0.rules_id.0", "def-000-be9"), + ), + }, + { + PreConfig: func() { + // Delete the framework directly via API + _, _, err := providers.frameworkProvider.DatadogApiInstances.GetSecurityMonitoringApiV2().DeleteCustomFramework(providers.frameworkProvider.Auth, handle, version) + if err != nil { + t.Fatalf("Failed to delete framework: %v", err) + } + }, + Config: testAccCheckDatadogCreateFramework(version, handle), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(path, "handle", handle), + resource.TestCheckResourceAttr(path, "version", version), + resource.TestCheckResourceAttr(path, "requirements.0.name", "requirement1"), + resource.TestCheckResourceAttr(path, "requirements.0.controls.0.name", "control1"), + resource.TestCheckResourceAttr(path, "requirements.0.controls.0.rules_id.0", "def-000-be9"), + ), + }, + }, + }) +} + +func TestCustomFramework_UpdateIfFrameworkExists(t *testing.T) { + handle := "terraform-handle" + version := "1.0" + + ctx, providers, accProviders := testAccFrameworkMuxProviders(context.Background(), t) + path := "datadog_compliance_custom_framework.sample_rules" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: accProviders, + CheckDestroy: testAccCheckDatadogFrameworkDestroy(ctx, providers.frameworkProvider, path, version, handle), + Steps: []resource.TestStep{ + { + PreConfig: func() { + // Create the framework directly via API + _, _, err := providers.frameworkProvider.DatadogApiInstances.GetSecurityMonitoringApiV2().CreateCustomFramework(providers.frameworkProvider.Auth, *buildCreateFrameworkRequest(complianceCustomFrameworkModel{ + Handle: types.StringValue(handle), + Version: types.StringValue(version), + Name: types.StringValue("existing-framework"), + Requirements: []complianceCustomFrameworkRequirementsModel{ + { + Name: types.StringValue("existing-requirement"), + Controls: []complianceCustomFrameworkControlsModel{ + { + Name: types.StringValue("existing-control"), + RulesID: []string{"def-000-be9"}, + }, + }, + }, + }, + })) + if err != nil { + t.Fatalf("Failed to create framework: %v", err) + } + }, + Config: testAccCheckDatadogCreateFramework(version, handle), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(path, "handle", handle), + resource.TestCheckResourceAttr(path, "version", version), + resource.TestCheckResourceAttr(path, "requirements.0.name", "requirement1"), + resource.TestCheckResourceAttr(path, "requirements.0.controls.0.name", "control1"), + resource.TestCheckResourceAttr(path, "requirements.0.controls.0.rules_id.0", "def-000-be9"), + ), + }, + }, + }) +} + +func TestCustomFramework_RecreateOnImmutableFields(t *testing.T) { + handle := "terraform-handle" + version := "1.0" + newHandle := "terraform-handle-new" + newVersion := "2.0" + + ctx, providers, accProviders := testAccFrameworkMuxProviders(context.Background(), t) + path := "datadog_compliance_custom_framework.sample_rules" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: accProviders, + CheckDestroy: testAccCheckDatadogFrameworkDestroy(ctx, providers.frameworkProvider, path, version, handle), + Steps: []resource.TestStep{ + { + Config: testAccCheckDatadogCreateFramework(version, handle), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(path, "handle", handle), + resource.TestCheckResourceAttr(path, "version", version), + ), + }, + { + Config: testAccCheckDatadogCreateFramework(newVersion, newHandle), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(path, "handle", newHandle), + resource.TestCheckResourceAttr(path, "version", newVersion), + // Verify old resource is deleted + func(s *terraform.State) error { + _, httpResp, err := providers.frameworkProvider.DatadogApiInstances.GetSecurityMonitoringApiV2().GetCustomFramework(providers.frameworkProvider.Auth, handle, version) + if err == nil || (httpResp != nil && httpResp.StatusCode != 400) { + return fmt.Errorf("old framework with handle %s and version %s still exists", handle, version) + } + return nil + }, + ), + }, + }, + }) +} + +// There is no way to validate the duplicate rule IDs in the config before they are removed from the state in Terraform +// because the state model converts the rules_id into sets, the duplicate rule IDs are removed +// This test validates that the duplicate rule IDs are removed from the state and only one rule ID is present +func TestCustomFramework_DuplicateRuleIds(t *testing.T) { + handle := "terraform-handle" + version := "1.0" + + ctx, providers, accProviders := testAccFrameworkMuxProviders(context.Background(), t) + path := "datadog_compliance_custom_framework.sample_rules" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: accProviders, + CheckDestroy: testAccCheckDatadogFrameworkDestroy(ctx, providers.frameworkProvider, path, version, handle), + Steps: []resource.TestStep{ + { + Config: testAccCheckDatadogDuplicateRulesId(version, handle), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(path, "handle", handle), + resource.TestCheckResourceAttr(path, "version", version), + resource.TestCheckResourceAttr(path, "requirements.0.controls.0.name", "control1"), + resource.TestCheckResourceAttr(path, "requirements.0.controls.0.rules_id.0", "def-000-be9"), + resource.TestCheckResourceAttr(path, "requirements.0.controls.0.rules_id.#", "1"), + ), + }, + }, + }) +} + +func TestCustomFramework_DuplicateHandle(t *testing.T) { + handle := "terraform-handle" + version := "1.0" + + ctx, providers, accProviders := testAccFrameworkMuxProviders(context.Background(), t) + path := "datadog_compliance_custom_framework.sample_rules" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: accProviders, + CheckDestroy: testAccCheckDatadogFrameworkDestroy(ctx, providers.frameworkProvider, path, version, handle), + Steps: []resource.TestStep{ + { + Config: testAccCheckDatadogCreateFramework(version, handle), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(path, "handle", handle), + resource.TestCheckResourceAttr(path, "version", version), + ), + }, + { + Config: testAccCheckDatadogCreateSecondFramework("different-version", handle), + ExpectError: regexp.MustCompile("Framework with same handle already exists"), + }, + }, + }) +} + +func testAccCheckDatadogCreateFrameworkWithMultipleRequirements(version string, handle string) string { + return fmt.Sprintf(` + resource "datadog_compliance_custom_framework" "sample_rules" { + version = "%s" + handle = "%s" + name = "new-name" + icon_url = "test url" + requirements { + name = "security-requirement" + controls { + name = "security-control" + rules_id = ["def-000-cea", "def-000-be9"] + } + } + requirements { + name = "compliance-requirement" + controls { + name = "compliance-control" + rules_id = ["def-000-be9", "def-000-cea"] + } + } + } + `, version, handle) +} + +func testAccCheckDatadogUpdateFrameworkWithMultipleRequirements(version, handle string) string { + return fmt.Sprintf(` + resource "datadog_compliance_custom_framework" "sample_rules" { + version = "%s" + handle = "%s" + name = "new-name" + icon_url = "test url" + requirements { + name = "security-requirement" + controls { + name = "security-control" + rules_id = ["def-000-cea"] + } + } + requirements { + name = "requirement" + controls { + name = "control" + rules_id = ["def-000-be9"] + } + } + requirements { + name = "requirement-2" + controls { + name = "control-2" + rules_id = ["def-000-be9"] + } + controls { + name = "control-3" + rules_id = ["def-000-cea", "def-000-be9"] + } + } + } + `, version, handle) +} + +func testAccCheckDatadogCreateFramework(version string, handle string) string { + return fmt.Sprintf(` + resource "datadog_compliance_custom_framework" "sample_rules" { + version = "%s" + handle = "%s" + name = "new-framework-terraform" + icon_url = "test-url" + requirements { + name = "requirement1" + controls { + name = "control1" + rules_id = ["def-000-be9"] + } + } + } + `, version, handle) +} + +func testAccCheckDatadogCreateSecondFramework(version string, handle string) string { + return fmt.Sprintf(` + resource "datadog_compliance_custom_framework" "sample_framework" { + version = "%s" + handle = "%s" + name = "new-framework-terraform" + icon_url = "test-url" + requirements { + name = "requirement1" + controls { + name = "control1" + rules_id = ["def-000-be9"] + } + } + } + `, version, handle) +} + +func testAccCheckDatadogCreateFrameworkWithoutOptionalFields(version string, handle string) string { + return fmt.Sprintf(` + resource "datadog_compliance_custom_framework" "sample_rules" { + version = "%s" + handle = "%s" + name = "new-framework-terraform" + requirements { + name = "requirement1" + controls { + name = "control1" + rules_id = ["def-000-be9"] + } + } + } + `, version, handle) +} + +func testAccCheckDatadogCreateFrameworkRuleIdsInvalid(version string, handle string) string { + return fmt.Sprintf(` + resource "datadog_compliance_custom_framework" "sample_rules" { + version = "%s" + handle = "%s" + name = "new-framework-terraform" + icon_url = "test url" + requirements { + name = "requirement1" + controls { + name = "control1" + rules_id = ["invalid-rule-id"] + } + } + } + `, version, handle) +} + +func testAccCheckDatadogCreateFrameworkWithNoControls(version string, handle string) string { + return fmt.Sprintf(` + resource "datadog_compliance_custom_framework" "sample_rules" { + version = "%s" + handle = "%s" + name = "new-framework-terraform" + icon_url = "test url" + requirements { + name = "requirement1" + } + } + `, version, handle) +} + +func testAccCheckDatadogCreateFrameworkWithNoRulesId(version string, handle string) string { + return fmt.Sprintf(` + resource "datadog_compliance_custom_framework" "sample_rules" { + version = "%s" + handle = "%s" + name = "new-framework-terraform" + icon_url = "test url" + requirements { + name = "requirement1" + controls { + name = "control1" + rules_id = [] + } + } + } + `, version, handle) +} + +func testAccCheckDatadogCreateFrameworkNoRequirements(version string, handle string) string { + return fmt.Sprintf(` + resource "datadog_compliance_custom_framework" "sample_rules" { + version = "%s" + handle = "%s" + name = "new-framework-terraform" + icon_url = "test url" + } + `, version, handle) +} + +func testAccCheckDatadogCreateInvalidFrameworkName(version string, handle string) string { + return fmt.Sprintf(` + resource "datadog_compliance_custom_framework" "sample_rules" { + version = "%s" + handle = "%s" + name = "" + icon_url = "test url" + requirements { + name = "requirement1" + controls { + name = "control1" + rules_id = ["def-000-be9"] + } + } + } + `, version, handle) +} + +func testAccCheckDatadogEmptyRequirementName(version string, handle string) string { + return fmt.Sprintf(` + resource "datadog_compliance_custom_framework" "sample_rules" { + version = "%s" + handle = "%s" + name = "name" + icon_url = "test url" + requirements { + name = "" + controls { + name = "control1" + rules_id = ["def-000-be9"] + } + } + } + `, version, handle) +} + +func testAccCheckDatadogEmptyControlName(version string, handle string) string { + return fmt.Sprintf(` + resource "datadog_compliance_custom_framework" "sample_rules" { + version = "%s" + handle = "%s" + name = "name" + icon_url = "test url" + requirements { + name = "requirement1" + controls { + name = "" + rules_id = ["def-000-be9"] + } + } + } + `, version, handle) +} + +func testAccCheckDatadogCreateEmptyHandle(version string) string { + return fmt.Sprintf(` + resource "datadog_compliance_custom_framework" "sample_rules" { + version = "%s" + handle = "" + name = "framework-name" + icon_url = "test url" + requirements { + name = "requirement1" + controls { + name = "control1" + rules_id = ["def-000-be9"] + } + } + } + `, version) +} + +func testAccCheckDatadogCreateEmptyVersion(handle string) string { + return fmt.Sprintf(` + resource "datadog_compliance_custom_framework" "sample_rules" { + version = "" + handle = "%s" + name = "framework-name" + icon_url = "test url" + requirements { + name = "requirement1" + controls { + name = "control1" + rules_id = ["def-000-be9"] + } + } + } + `, handle) +} + +func testAccCheckDatadogDuplicateRequirementName(version string, handle string) string { + return fmt.Sprintf(` + resource "datadog_compliance_custom_framework" "sample_rules" { + version = "%s" + handle = "%s" + name = "framework-name" + icon_url = "test url" + requirements { + name = "requirement1" + controls { + name = "control1" + rules_id = ["def-000-be9"] + } + } + requirements { + name = "requirement1" + controls { + name = "control2" + rules_id = ["def-000-cea"] + } + } + } + `, version, handle) +} + +func testAccCheckDatadogDuplicateRequirement(version string, handle string) string { + return fmt.Sprintf(` + resource "datadog_compliance_custom_framework" "sample_rules" { + version = "%s" + handle = "%s" + name = "framework-name" + icon_url = "test url" + requirements { + name = "requirement1" + controls { + name = "control1" + rules_id = ["def-000-be9"] + } + } + requirements { + name = "requirement1" + controls { + name = "control1" + rules_id = ["def-000-be9"] + } + } + } + `, version, handle) +} + +func testAccCheckDatadogDuplicateControlName(version string, handle string) string { + return fmt.Sprintf(` + resource "datadog_compliance_custom_framework" "sample_rules" { + version = "%s" + handle = "%s" + name = "framework-name" + icon_url = "test url" + requirements { + name = "requirement1" + controls { + name = "control1" + rules_id = ["def-000-be9"] + } + } + requirements { + name = "requirement2" + controls { + name = "control1" + rules_id = ["def-000-be9"] + } + controls { + name = "control1" + rules_id = ["def-000-cea"] + } + } + } + `, version, handle) +} + +func testAccCheckDatadogDuplicateControl(version string, handle string) string { + return fmt.Sprintf(` + resource "datadog_compliance_custom_framework" "sample_rules" { + version = "%s" + handle = "%s" + name = "framework-name" + icon_url = "test url" + requirements { + name = "requirement1" + controls { + name = "control1" + rules_id = ["def-000-be9"] + } + } + requirements { + name = "requirement2" + controls { + name = "control1" + rules_id = ["def-000-be9"] + } + controls { + name = "control1" + rules_id = ["def-000-be9"] + } + } + } + `, version, handle) +} + +func testAccCheckDatadogDuplicateRulesId(version string, handle string) string { + return fmt.Sprintf(` + resource "datadog_compliance_custom_framework" "sample_rules" { + version = "%s" + handle = "%s" + name = "framework-name" + icon_url = "test url" + requirements { + name = "requirement1" + controls { + name = "control1" + rules_id = ["def-000-be9", "def-000-be9"] + } + } + } + `, version, handle) +} + +func testAccCheckDatadogFrameworkDestroy(ctx context.Context, accProvider *fwprovider.FrameworkProvider, resourceName string, version string, handle string) func(*terraform.State) error { + return func(s *terraform.State) error { + apiInstances := accProvider.DatadogApiInstances + resource, ok := s.RootModule().Resources[resourceName] + if !ok { + return nil + } + if resource.Primary == nil { + return nil + } + handle := resource.Primary.Attributes["handle"] + version := resource.Primary.Attributes["version"] + _, httpRes, err := apiInstances.GetSecurityMonitoringApiV2().GetCustomFramework(ctx, handle, version) + if err != nil { + if httpRes != nil && httpRes.StatusCode == 400 { + return nil + } + return err + } + + return fmt.Errorf("framework destroy check failed") + } +} diff --git a/docs/resources/compliance_custom_framework.md b/docs/resources/compliance_custom_framework.md new file mode 100644 index 0000000000..4fa8d0d37a --- /dev/null +++ b/docs/resources/compliance_custom_framework.md @@ -0,0 +1,80 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "datadog_compliance_custom_framework Resource - terraform-provider-datadog" +subcategory: "" +description: |- + Provides a Datadog Compliance Custom Framework resource, which is used to create and manage compliance custom frameworks. +--- + +# datadog_compliance_custom_framework (Resource) + +Provides a Datadog Compliance Custom Framework resource, which is used to create and manage compliance custom frameworks. + +## Example Usage + +```terraform +resource "datadog_compliance_custom_framework" "framework" { + name = "my-custom-framework-terraform-2" + version = "2.0.0" + handle = "my-custom-framework-terraform-2" + + requirements { + name = "requirement2" + controls { + name = "control2" + rules_id = ["def-000-h9o", "def-000-b6i", "def-000-yed", "def-000-h5a", "def-000-aw5"] + } + controls { + name = "control1" + rules_id = ["def-000-j9v", "def-000-465", "def-000-vq1", "def-000-4hf", "def-000-s2d", "def-000-vnl"] + } + } + + requirements { + name = "requirement1" + controls { + name = "control2" + rules_id = ["def-000-wuf", "def-000-7og"] + } + controls { + name = "control5" + rules_id = ["def-000-mdt", "def-000-zrx", "def-000-z6k"] + } + } +} +``` + + +## Schema + +### Required + +- `handle` (String) The framework handle. String length must be at least 1. This field is immutable. +- `name` (String) The framework name. String length must be at least 1. +- `version` (String) The framework version. String length must be at least 1. This field is immutable. +- `requirements` (Block List) The requirements of the framework. Length must be at least 1. (see [below for nested schema](#nestedblock--requirements)) + + +### Optional + +- `icon_url` (String) The URL of the icon representing the framework + +### Read-Only + +- `id` (String) The ID of the compliance custom framework resource. + + +### Nested Schema for `requirements` + +Required: + +- `name` (String) The name of the requirement. String length must be at least 1. +- `controls` (Block List) The controls of the requirement. Length must be at least 1. (see [below for nested schema](#nestedblock--requirements--controls)) + + +### Nested Schema for `requirements.controls` + +Required: + +- `name` (String) The name of the control. String length must be at least 1. +- `rules_id` (Set of String) The set of rules IDs for the control. Length must be at least 1. diff --git a/examples/resources/datadog_compliance_custom_framework/resource.tf b/examples/resources/datadog_compliance_custom_framework/resource.tf new file mode 100644 index 0000000000..12def186b5 --- /dev/null +++ b/examples/resources/datadog_compliance_custom_framework/resource.tf @@ -0,0 +1,29 @@ +resource "datadog_compliance_custom_framework" "framework" { + name = "my-custom-framework-terraform-2" + version = "2.0.0" + handle = "my-custom-framework-terraform-2" + + requirements { + name = "requirement2" + controls { + name = "control2" + rules_id = ["def-000-h9o", "def-000-b6i", "def-000-yed", "def-000-h5a", "def-000-aw5"] + } + controls { + name = "control1" + rules_id = ["def-000-j9v", "def-000-465", "def-000-vq1", "def-000-4hf", "def-000-s2d", "def-000-vnl"] + } + } + + requirements { + name = "requirement1" + controls { + name = "control2" + rules_id = ["def-000-wuf", "def-000-7og"] + } + controls { + name = "control5" + rules_id = ["def-000-mdt", "def-000-zrx", "def-000-z6k"] + } + } +} \ No newline at end of file diff --git a/scripts/generate-docs.sh b/scripts/generate-docs.sh index 7eb1cd4bc5..522010be16 100755 --- a/scripts/generate-docs.sh +++ b/scripts/generate-docs.sh @@ -4,6 +4,7 @@ # Add here the files to be excluded from the doc generation exclude_files=( "docs/resources/integration_aws_account.md" + "docs/resources/compliance_custom_framework.md" ) # Check if manual changes were made to any excluded files and exit