Skip to content

Commit

Permalink
Merge pull request #4734 from mrusso19/add-content-scanning-expression
Browse files Browse the repository at this point in the history
  • Loading branch information
jacobbednarz authored Dec 26, 2024
2 parents 873da35 + 61dc293 commit 1ef0bed
Show file tree
Hide file tree
Showing 9 changed files with 408 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .changelog/4734.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
cloudflare_content_scanning_expression
```
49 changes: 49 additions & 0 deletions docs/resources/content_scanning_expression.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
page_title: "cloudflare_content_scanning_expression Resource - Cloudflare"
subcategory: ""
description: |-
Provides a Cloudflare Content Scanning Expression resource for managing custom scan expression within a specific zone.
---

# cloudflare_content_scanning_expression (Resource)

Provides a Cloudflare Content Scanning Expression resource for managing custom scan expression within a specific zone.

## Example Usage

```terraform
# Enable Content Scanning before trying to add custom scan expressions
resource "cloudflare_content_scanning" "example" {
zone_id = "399c6f4950c01a5a141b99ff7fbcbd8b"
enabled = true
}
resource "cloudflare_content_scanning_expression" "first_example" {
zone_id = cloudflare_content_scanning.example.zone_id
payload = "lookup_json_string(http.request.body.raw, \"file\")"
}
resource "cloudflare_content_scanning_expression" "second_example" {
zone_id = cloudflare_content_scanning.example.zone_id
payload = "lookup_json_string(http.request.body.raw, \"document\")"
}
```
<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `payload` (String) Custom scan expression to tell the content scanner where to find the content objects.
- `zone_id` (String) The zone identifier to target for the resource.

### Read-Only

- `id` (String) The identifier of this resource.

## Import

Import is supported using the following syntax:

```shell
terraform import cloudflare_content_scanning_expression.example <zone_id>/<resource_id>
```
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
terraform import cloudflare_content_scanning_expression.example <zone_id>/<resource_id>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Enable Content Scanning before trying to add custom scan expressions
resource "cloudflare_content_scanning" "example" {
zone_id = "399c6f4950c01a5a141b99ff7fbcbd8b"
enabled = true
}

resource "cloudflare_content_scanning_expression" "first_example" {
zone_id = cloudflare_content_scanning.example.zone_id
payload = "lookup_json_string(http.request.body.raw, \"file\")"
}

resource "cloudflare_content_scanning_expression" "second_example" {
zone_id = cloudflare_content_scanning.example.zone_id
payload = "lookup_json_string(http.request.body.raw, \"document\")"
}
2 changes: 2 additions & 0 deletions internal/framework/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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_expression"
"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"
Expand Down Expand Up @@ -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_expression.NewResource,
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package content_scanning_expression

import "github.com/hashicorp/terraform-plugin-framework/types"

type ContentScanningExpressionModel struct {
ZoneID types.String `tfsdk:"zone_id"`
ID types.String `tfsdk:"id"`
Payload types.String `tfsdk:"payload"`
}
196 changes: 196 additions & 0 deletions internal/framework/service/content_scanning_expression/resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package content_scanning_expression

import (
"context"
"fmt"
"strings"

"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 = &ContentScanningExpressionResource{}
_ resource.ResourceWithImportState = &ContentScanningExpressionResource{}
)

func NewResource() resource.Resource {
return &ContentScanningExpressionResource{}
}

type ContentScanningExpressionResource struct {
client *muxclient.Client
}

func (r *ContentScanningExpressionResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_content_scanning_expression"
}

func (r *ContentScanningExpressionResource) 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 *ContentScanningExpressionResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data ContentScanningExpressionModel
diags := req.Plan.Get(ctx, &data)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
params := cloudflare.ContentScanningAddCustomExpressionsParams{
Payloads: []cloudflare.ContentScanningCustomPayload{
{
Payload: data.Payload.ValueString(),
},
},
}
expressions, err := r.client.V1.ContentScanningAddCustomExpressions(ctx, cloudflare.ZoneIdentifier(data.ZoneID.ValueString()), params)
if err != nil {
resp.Diagnostics.AddError("Error creating a custom scan expression for Content Scanning", err.Error())
return
}

// The Add API returns a list of all exiting custom scan expression
// loop until we find the newly created one, matching on payload
// payload uniqueness is enforced by the service
for _, exp := range expressions {
if exp.Payload == data.Payload.ValueString() {
data.ID = types.StringValue(exp.ID)
break
}
}

diags = resp.State.Set(ctx, &data)
resp.Diagnostics.Append(diags...)
}

func (r *ContentScanningExpressionResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var state ContentScanningExpressionModel
diags := req.State.Get(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

zoneID := state.ZoneID.ValueString()
var foundExp cloudflare.ContentScanningCustomExpression
expressions, err := r.client.V1.ContentScanningListCustomExpressions(ctx, cloudflare.ZoneIdentifier(zoneID), cloudflare.ContentScanningListCustomExpressionsParams{})
if err != nil {
resp.Diagnostics.AddError("Error listing customs scan expressions for Content Scanning", err.Error())
return
}

// content scanning doens't offer a single get operation so
// loop until we find the matching ID.
for _, exp := range expressions {
if exp.ID == state.ID.ValueString() {
foundExp = exp
break
}
}

state.ID = types.StringValue(foundExp.ID)
state.Payload = types.StringValue(foundExp.Payload)

diags = resp.State.Set(ctx, &state)
resp.Diagnostics.Append(diags...)
}

func (r *ContentScanningExpressionResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var plan ContentScanningExpressionModel
diags := req.Plan.Get(ctx, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
var state ContentScanningExpressionModel
diags = req.State.Get(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
zoneID := cloudflare.ZoneIdentifier(plan.ZoneID.ValueString())
plan.ID = state.ID

// API does not offer an update operation so we use delete/create
delParams := cloudflare.ContentScanningDeleteCustomExpressionsParams{ID: plan.ID.ValueString()}
_, err := r.client.V1.ContentScanningDeleteCustomExpression(ctx, zoneID, delParams)
if err != nil {
resp.Diagnostics.AddError("Error in Update while deleting custom scan expression for Content Scanning", err.Error())
return
}
createParams := cloudflare.ContentScanningAddCustomExpressionsParams{
Payloads: []cloudflare.ContentScanningCustomPayload{
{
Payload: plan.Payload.ValueString(),
},
},
}
expressions, err := r.client.V1.ContentScanningAddCustomExpressions(ctx, zoneID, createParams)
if err != nil {
resp.Diagnostics.AddError("Error in Update while creating a custom scan expression for Content Scanning", err.Error())
return
}

// The Add API returns a list of all exiting custom scan expression
// loop until we find the newly created one, matching on payload
// payload uniqueness is enforced by the service
for _, exp := range expressions {
if exp.Payload == plan.Payload.ValueString() {
plan.ID = types.StringValue(exp.ID)
break
}
}

diags = resp.State.Set(ctx, &plan)
resp.Diagnostics.Append(diags...)
}

func (r *ContentScanningExpressionResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var state ContentScanningExpressionModel
diags := req.State.Get(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
zoneID := cloudflare.ZoneIdentifier(state.ZoneID.ValueString())
deleteParam := cloudflare.ContentScanningDeleteCustomExpressionsParams{ID: state.ID.ValueString()}
_, err := r.client.V1.ContentScanningDeleteCustomExpression(ctx, zoneID, deleteParam)
if err != nil {
resp.Diagnostics.AddError("Error deleting custom scan expression for Content Scanning", err.Error())
return
}
}

func (r *ContentScanningExpressionResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
idparts := strings.Split(req.ID, "/")
if len(idparts) != 2 {
resp.Diagnostics.AddError("error importing content scanning custom expression", "invalid ID specified. Please specify the ID as \"zone_id/resource_id\"")
return
}
resp.Diagnostics.Append(resp.State.SetAttribute(
ctx, path.Root("zone_id"), idparts[0],
)...)
resp.Diagnostics.Append(resp.State.SetAttribute(
ctx, path.Root("id"), idparts[1],
)...)
}
Loading

0 comments on commit 1ef0bed

Please sign in to comment.