Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add content scanning expression resource #4734

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading