From 0f6fb3eebd6e9eef036dd55f9dd7915dc3a36d96 Mon Sep 17 00:00:00 2001 From: Michele Russo Date: Thu, 5 Dec 2024 16:54:57 +0100 Subject: [PATCH 1/6] Add content scanning resource --- .../service/content_scanning/model.go | 8 + .../service/content_scanning/resource.go | 153 ++++++++++++++++++ .../service/content_scanning/resource_test.go | 89 ++++++++++ .../service/content_scanning/schema.go | 25 +++ 4 files changed, 275 insertions(+) create mode 100644 internal/framework/service/content_scanning/model.go create mode 100644 internal/framework/service/content_scanning/resource.go create mode 100644 internal/framework/service/content_scanning/resource_test.go create mode 100644 internal/framework/service/content_scanning/schema.go diff --git a/internal/framework/service/content_scanning/model.go b/internal/framework/service/content_scanning/model.go new file mode 100644 index 0000000000..776c0efb53 --- /dev/null +++ b/internal/framework/service/content_scanning/model.go @@ -0,0 +1,8 @@ +package content_scanning + +import "github.com/hashicorp/terraform-plugin-framework/types" + +type ContentScanningModel struct { + ZoneID types.String `tfsdk:"zone_id"` + Enabled types.Bool `tfsdk:"enabled"` +} diff --git a/internal/framework/service/content_scanning/resource.go b/internal/framework/service/content_scanning/resource.go new file mode 100644 index 0000000000..bffc989e4b --- /dev/null +++ b/internal/framework/service/content_scanning/resource.go @@ -0,0 +1,153 @@ +package content_scanning + +import ( + "context" + "fmt" + + "github.com/cloudflare/cloudflare-go" + "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/muxclient" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ resource.Resource = &ContentScanningResource{} +) + +func NewResource() resource.Resource { + return &ContentScanningResource{} +} + +type ContentScanningResource struct { + client *muxclient.Client +} + +func (r *ContentScanningResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_content_scanning" +} + +func (r *ContentScanningResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*muxclient.Client) + + if !ok { + resp.Diagnostics.AddError( + "unexpected resource configure type", + fmt.Sprintf("Expected *muxclient.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + r.client = client +} + +func (r *ContentScanningResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan ContentScanningModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + zoneID := cloudflare.ZoneIdentifier(plan.ZoneID.ValueString()) + if plan.Enabled.ValueBool() { + params := cloudflare.ContentScanningEnableParams{} + _, err := r.client.V1.ContentScanningEnable(ctx, zoneID, params) + if err != nil { + resp.Diagnostics.AddError("Error enabling (Create) Content Scanning", err.Error()) + return + } + } else { + params := cloudflare.ContentScanningDisableParams{} + _, err := r.client.V1.ContentScanningDisable(ctx, zoneID, params) + if err != nil { + resp.Diagnostics.AddError("Error disabling (Create) Content Scanning", err.Error()) + return + } + } + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +func (r *ContentScanningResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state ContentScanningModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + zoneID := state.ZoneID.ValueString() + status, err := r.client.V1.ContentScanningStatus(ctx, cloudflare.ZoneIdentifier(zoneID), cloudflare.ContentScanningStatusParams{}) + if err != nil { + resp.Diagnostics.AddError("Error reading Content Scanning status", err.Error()) + return + } + switch status.Result.Value { + case "enabled": + state.Enabled = types.BoolValue(true) + case "disabled": + state.Enabled = types.BoolValue(false) + default: + resp.Diagnostics.AddError("Unrecognized state", fmt.Sprintf("Unrecognized state = %s for Content Scanning", status.Result.Value)) + return + } + + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) +} + +func (r *ContentScanningResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan ContentScanningModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + zoneID := cloudflare.ZoneIdentifier(plan.ZoneID.ValueString()) + if plan.Enabled.ValueBool() { + params := cloudflare.ContentScanningEnableParams{} + _, err := r.client.V1.ContentScanningEnable(ctx, zoneID, params) + if err != nil { + resp.Diagnostics.AddError("Error enabling (Update) Content Scanning", err.Error()) + return + } + } else { + params := cloudflare.ContentScanningDisableParams{} + _, err := r.client.V1.ContentScanningDisable(ctx, zoneID, params) + if err != nil { + resp.Diagnostics.AddError("Error disabling (Update) Content Scanning", err.Error()) + return + } + } + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +// Delete disables the Content Scanning feature +func (r *ContentScanningResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state ContentScanningModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + zoneID := cloudflare.ZoneIdentifier(state.ZoneID.ValueString()) + params := cloudflare.ContentScanningDisableParams{} + _, err := r.client.V1.ContentScanningDisable(ctx, zoneID, params) + if err != nil { + resp.Diagnostics.AddError("Error disabling (Update) Content Scanning", err.Error()) + return + } +} + +func (r *ContentScanningResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + // req.ID is the zoneID for which you want to import the state of the Content Scanning feature + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("zone_id"), req.ID)...) +} diff --git a/internal/framework/service/content_scanning/resource_test.go b/internal/framework/service/content_scanning/resource_test.go new file mode 100644 index 0000000000..e8c8eadfc9 --- /dev/null +++ b/internal/framework/service/content_scanning/resource_test.go @@ -0,0 +1,89 @@ +package content_scanning_test + +import ( + "context" + "errors" + "fmt" + "log" + "os" + "testing" + + cfv1 "github.com/cloudflare/cloudflare-go" + "github.com/cloudflare/terraform-provider-cloudflare/internal/acctest" + "github.com/cloudflare/terraform-provider-cloudflare/internal/utils" + + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func init() { + resource.AddTestSweepers("cloudflare_content_scanning", &resource.Sweeper{ + Name: "cloudflare_content_scanning", + F: testSweepCloudflareCS, + }) +} + +func testSweepCloudflareCS(r string) error { + ctx := context.Background() + client, clientErr := acctest.SharedV1Client() + if clientErr != nil { + tflog.Error(ctx, fmt.Sprintf("Failed to create Cloudflare client: %s", clientErr)) + } + + zoneID := os.Getenv("CLOUDFLARE_ZONE_ID") + if zoneID == "" { + return errors.New("CLOUDFLARE_ZONE_ID must be set") + } + + status, err := client.ContentScanningStatus(ctx, cfv1.ZoneIdentifier(zoneID), cfv1.ContentScanningStatusParams{}) + if err != nil { + tflog.Error(ctx, fmt.Sprintf("Failed to GET Content Scanning status: %s", err)) + } + + if status.Result.Value == "disabled" { + log.Print("[DEBUG] Content Scanning already disabled") + return nil + } + _, err = client.ContentScanningDisable(ctx, cfv1.ZoneIdentifier(zoneID), cfv1.ContentScanningDisableParams{}) + if err != nil { + tflog.Error(ctx, fmt.Sprintf("Failed to disable Content Scanning: %s", err)) + return err + } + + return nil +} + +func TestAccCloudflareContentScanning_Basic(t *testing.T) { + rnd := utils.GenerateRandomResourceName() + name := fmt.Sprintf("cloudflare_content_scanning.%s", rnd) + zoneID := os.Getenv("CLOUDFLARE_ZONE_ID") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.TestAccPreCheck(t) + }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccContentScanningSimple(rnd, zoneID, "true"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "enabled", "true"), + ), + }, + { + Config: testAccContentScanningSimple(rnd, zoneID, "false"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "enabled", "false"), + ), + }, + }, + }) +} + +func testAccContentScanningSimple(ID, zoneID, enabled string) string { + return fmt.Sprintf(` + resource "cloudflare_content_scanning" "%[1]s" { + zone_id = "%[2]s" + enabled = "%[3]s" + }`, ID, zoneID, enabled) +} diff --git a/internal/framework/service/content_scanning/schema.go b/internal/framework/service/content_scanning/schema.go new file mode 100644 index 0000000000..761bf9fede --- /dev/null +++ b/internal/framework/service/content_scanning/schema.go @@ -0,0 +1,25 @@ +package content_scanning + +import ( + "context" + + "github.com/cloudflare/terraform-provider-cloudflare/internal/consts" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" +) + +func (r *ContentScanningResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Provides a Content Scanning resource to be used for managing the status of the Content Scanning feature within a specific zone.", + Attributes: map[string]schema.Attribute{ + consts.ZoneIDSchemaKey: schema.StringAttribute{ + Description: consts.ZoneIDSchemaDescription, + Required: true, + }, + "enabled": schema.BoolAttribute{ + Description: "State of the Content Scanning feature", + Required: true, + }, + }, + } +} From 827dd4465ba5be28eaa773d3efaf481b271afd86 Mon Sep 17 00:00:00 2001 From: Michele Russo Date: Thu, 5 Dec 2024 16:55:22 +0100 Subject: [PATCH 2/6] Add content scanning examples --- examples/resources/cloudflare_content_scanning/import.sh | 1 + examples/resources/cloudflare_content_scanning/resource.tf | 5 +++++ 2 files changed, 6 insertions(+) create mode 100644 examples/resources/cloudflare_content_scanning/import.sh create mode 100644 examples/resources/cloudflare_content_scanning/resource.tf diff --git a/examples/resources/cloudflare_content_scanning/import.sh b/examples/resources/cloudflare_content_scanning/import.sh new file mode 100644 index 0000000000..654f634311 --- /dev/null +++ b/examples/resources/cloudflare_content_scanning/import.sh @@ -0,0 +1 @@ +terraform import cloudflare_content_scanning.example \ No newline at end of file diff --git a/examples/resources/cloudflare_content_scanning/resource.tf b/examples/resources/cloudflare_content_scanning/resource.tf new file mode 100644 index 0000000000..a339c3ab1b --- /dev/null +++ b/examples/resources/cloudflare_content_scanning/resource.tf @@ -0,0 +1,5 @@ +# Enable Content Scanning +resource "cloudflare_content_scanning" "example" { + zone_id = "399c6f4950c01a5a141b99ff7fbcbd8b" + enabled = true +} \ No newline at end of file From 4fbc90fef7f0cdf5086b74ace4824fcd699f8bd8 Mon Sep 17 00:00:00 2001 From: Michele Russo Date: Thu, 5 Dec 2024 16:55:35 +0100 Subject: [PATCH 3/6] Add content scanning to provider --- internal/framework/provider/provider.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/framework/provider/provider.go b/internal/framework/provider/provider.go index 5330e91b58..97561762a1 100644 --- a/internal/framework/provider/provider.go +++ b/internal/framework/provider/provider.go @@ -18,6 +18,7 @@ import ( "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/access_mutual_tls_hostname_settings" "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/api_token_permissions_groups" "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/cloud_connector_rules" + "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/content_scanning" "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/d1" "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/dcv_delegation" "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/dlp_datasets" @@ -395,6 +396,7 @@ func (p *CloudflareProvider) Resources(ctx context.Context) []func() resource.Re zero_trust_infrastructure_access_target.NewResource, leaked_credential_check.NewResource, leaked_credential_check_rule.NewResource, + content_scanning.NewResource, } } From 0198ad0f9d416cf091d51a2d2c883e5b403b5cb1 Mon Sep 17 00:00:00 2001 From: Michele Russo Date: Thu, 5 Dec 2024 16:55:45 +0100 Subject: [PATCH 4/6] `make docs` --- docs/resources/content_scanning.md | 35 ++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 docs/resources/content_scanning.md diff --git a/docs/resources/content_scanning.md b/docs/resources/content_scanning.md new file mode 100644 index 0000000000..8968fd8e4c --- /dev/null +++ b/docs/resources/content_scanning.md @@ -0,0 +1,35 @@ +--- +page_title: "cloudflare_content_scanning Resource - Cloudflare" +subcategory: "" +description: |- + Provides a Content Scanning resource to be used for managing the status of the Content Scanning feature within a specific zone. +--- + +# cloudflare_content_scanning (Resource) + +Provides a Content Scanning resource to be used for managing the status of the Content Scanning feature within a specific zone. + +## Example Usage + +```terraform +# Enable Content Scanning +resource "cloudflare_content_scanning" "example" { + zone_id = "399c6f4950c01a5a141b99ff7fbcbd8b" + enabled = true +} +``` + +## Schema + +### Required + +- `enabled` (Boolean) State of the Content Scanning feature +- `zone_id` (String) The zone identifier to target for the resource. + +## Import + +Import is supported using the following syntax: + +```shell +terraform import cloudflare_content_scanning.example +``` From d8cdd93e668bf532155ca0aa3f33ea7cf89b7f43 Mon Sep 17 00:00:00 2001 From: Michele Russo Date: Thu, 5 Dec 2024 17:01:01 +0100 Subject: [PATCH 5/6] Add changelog --- .changelog/4719.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/4719.txt diff --git a/.changelog/4719.txt b/.changelog/4719.txt new file mode 100644 index 0000000000..c1f338ed38 --- /dev/null +++ b/.changelog/4719.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +cloudflare_content_scanning +``` \ No newline at end of file From 36306ce225725ec58816e84142dc1194254fc112 Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Thu, 26 Dec 2024 14:01:18 +1100 Subject: [PATCH 6/6] fix lint --- internal/framework/service/content_scanning/resource.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/framework/service/content_scanning/resource.go b/internal/framework/service/content_scanning/resource.go index bffc989e4b..8d739f7c6f 100644 --- a/internal/framework/service/content_scanning/resource.go +++ b/internal/framework/service/content_scanning/resource.go @@ -129,7 +129,7 @@ func (r *ContentScanningResource) Update(ctx context.Context, req resource.Updat resp.Diagnostics.Append(diags...) } -// Delete disables the Content Scanning feature +// Delete disables the Content Scanning feature. func (r *ContentScanningResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { var state ContentScanningModel diags := req.State.Get(ctx, &state)