diff --git a/.changelog/4893.txt b/.changelog/4893.txt new file mode 100644 index 0000000000..322e227392 --- /dev/null +++ b/.changelog/4893.txt @@ -0,0 +1,12 @@ +```release-note:note +`cloudflare_api_shield_operation` has been migrated to the `terraform-plugin-framework`. +``` + +```release-note:bug +`cloudflare_api_shield_operation` fixed a bug when using variable names other than `var1 ... varN` in endpoint definitions causing these resources to be recreated when nothing has changed. +If this affects you, after upgrading to this version, the resource has to be recreated once more to fix the state, after which the bug is fixed. +``` + +```release-note:internal +resource/cloudflare_api_shield_operation: migrate from SDKv2 to `terraform-plugin-framework` +``` diff --git a/internal/framework/provider/provider.go b/internal/framework/provider/provider.go index 1b8247356b..19058e4386 100644 --- a/internal/framework/provider/provider.go +++ b/internal/framework/provider/provider.go @@ -16,6 +16,7 @@ import ( "github.com/cloudflare/terraform-provider-cloudflare/internal/consts" "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/muxclient" "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/access_mutual_tls_hostname_settings" + apishieldoperation "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/api_shield_operation" "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" @@ -399,6 +400,7 @@ func (p *CloudflareProvider) Resources(ctx context.Context) []func() resource.Re leaked_credential_check_rule.NewResource, content_scanning.NewResource, content_scanning_expression.NewResource, + apishieldoperation.NewResource, } } diff --git a/internal/framework/service/api_shield_operation/endpoint.go b/internal/framework/service/api_shield_operation/endpoint.go new file mode 100644 index 0000000000..c256195121 --- /dev/null +++ b/internal/framework/service/api_shield_operation/endpoint.go @@ -0,0 +1,106 @@ +package apishieldoperation + +import ( + "context" + "fmt" + "regexp" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +var varMatch = regexp.MustCompile(`{([\w\d]+)}`) + +// EndpointType implemented based on https://developer.hashicorp.com/terraform/plugin/framework/handling-data/types/custom + +// Ensure the implementation satisfies the expected interfaces +var _ basetypes.StringTypable = EndpointType{} +var _ basetypes.StringValuableWithSemanticEquals = EndpointValue{} + +type EndpointType struct { + basetypes.StringType +} + +func (t EndpointType) Equal(o attr.Type) bool { + other, ok := o.(EndpointType) + + if !ok { + return false + } + + return t.StringType.Equal(other.StringType) +} + +func (t EndpointType) String() string { + return "EndpointType" +} + +func (t EndpointType) ValueFromString(ctx context.Context, in basetypes.StringValue) (basetypes.StringValuable, diag.Diagnostics) { + value := EndpointValue{ + StringValue: in, + } + + return value, nil +} + +func (t EndpointType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + attrValue, err := t.StringType.ValueFromTerraform(ctx, in) + + if err != nil { + return nil, err + } + + stringValue, ok := attrValue.(basetypes.StringValue) + + if !ok { + return nil, fmt.Errorf("unexpected value type of %T", attrValue) + } + + stringValuable, diags := t.ValueFromString(ctx, stringValue) + + if diags.HasError() { + return nil, fmt.Errorf("unexpected error converting StringValue to StringValuable: %v", diags) + } + + return stringValuable, nil +} + +func (t EndpointType) ValueType(ctx context.Context) attr.Value { + return EndpointValue{} +} + +var _ basetypes.StringValuable = EndpointValue{} + +type EndpointValue struct { + basetypes.StringValue +} + +func (v EndpointValue) Equal(o attr.Value) bool { + other, ok := o.(EndpointValue) + + if !ok { + return false + } + + return v.StringValue.Equal(other.StringValue) +} + +func (v EndpointValue) Type(ctx context.Context) attr.Type { + return EndpointType{} +} + +func NewEndpointValue(value string) EndpointValue { + if value == "" { + return EndpointValue{types.StringNull()} + } + return EndpointValue{types.StringValue(value)} +} + +func (v EndpointValue) StringSemanticEquals(ctx context.Context, o basetypes.StringValuable) (bool, diag.Diagnostics) { + oStrVal, diag := o.ToStringValue(ctx) + result := varMatch.ReplaceAllString(v.StringValue.ValueString(), "{var}") == varMatch.ReplaceAllString(oStrVal.ValueString(), "{var}") + return result, diag +} diff --git a/internal/framework/service/api_shield_operation/endpoint_test.go b/internal/framework/service/api_shield_operation/endpoint_test.go new file mode 100644 index 0000000000..05043bcef6 --- /dev/null +++ b/internal/framework/service/api_shield_operation/endpoint_test.go @@ -0,0 +1,17 @@ +package apishieldoperation + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEndpointValueEquality(t *testing.T) { + e1 := NewEndpointValue("/foo/{fooId}/bar/{barId}/baz") + e2 := NewEndpointValue("/foo/{var1}/bar/{var2}/baz") + + ok, diag := e1.StringSemanticEquals(context.Background(), e2) + assert.Nil(t, diag) + assert.True(t, ok) +} diff --git a/internal/framework/service/api_shield_operation/model.go b/internal/framework/service/api_shield_operation/model.go new file mode 100644 index 0000000000..6e3c2ced9d --- /dev/null +++ b/internal/framework/service/api_shield_operation/model.go @@ -0,0 +1,11 @@ +package apishieldoperation + +import "github.com/hashicorp/terraform-plugin-framework/types" + +type APIShieldOperationModel struct { + ID types.String `tfsdk:"id"` + ZoneID types.String `tfsdk:"zone_id"` + Method types.String `tfsdk:"method"` + Host types.String `tfsdk:"host"` + Endpoint EndpointValue `tfsdk:"endpoint"` +} diff --git a/internal/framework/service/api_shield_operation/resource.go b/internal/framework/service/api_shield_operation/resource.go new file mode 100644 index 0000000000..047466d61e --- /dev/null +++ b/internal/framework/service/api_shield_operation/resource.go @@ -0,0 +1,148 @@ +package apishieldoperation + +import ( + "context" + "fmt" + "strings" + + "github.com/cloudflare/cloudflare-go" + "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/flatteners" + "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/muxclient" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &APIShieldOperationResource{} +var _ resource.ResourceWithImportState = &APIShieldOperationResource{} + +func NewResource() resource.Resource { + return &APIShieldOperationResource{} +} + +type APIShieldOperationResource struct { + client *muxclient.Client +} + +func (r *APIShieldOperationResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_api_shield_operation" +} + +func (r *APIShieldOperationResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + r.client = req.ProviderData.(*muxclient.Client) +} + +func (r *APIShieldOperationResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var model APIShieldOperationModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &model)...) + if resp.Diagnostics.HasError() { + return + } + + ops, err := r.client.V1.CreateAPIShieldOperations( + ctx, + cloudflare.ZoneIdentifier(model.ZoneID.ValueString()), + cloudflare.CreateAPIShieldOperationsParams{ + Operations: []cloudflare.APIShieldBasicOperation{ + { + Method: model.Method.ValueString(), + Host: model.Host.ValueString(), + Endpoint: model.Endpoint.ValueString(), + }, + }, + }, + ) + + if err != nil { + resp.Diagnostics.AddError("Error creating API Shield Operation", err.Error()) + return + } + + if len(ops) != 1 { + resp.Diagnostics.AddError("Error creating API Shield Operation", fmt.Sprintf("expected 1 operation in response but got %d", len(ops))) + return + } + + op := ops[0] + // The API normalizes the response, so we must not override it on create. + // See https://github.com/hashicorp/terraform/blob/main/docs/resource-instance-change-lifecycle.md + // Normalization: the remote API has returned some data in a different form than was recorded in the Previous Run State, but the meaning is unchanged. + // In this case, the provider should return the exact value from the Previous Run State, + // thereby preserving the value as it was written by the user in the configuration and thus avoiding unwanted cascading changes to elsewhere in the configuration. + model.ID = flatteners.String(op.ID) + resp.Diagnostics.Append(resp.State.Set(ctx, model)...) +} + +func (r *APIShieldOperationResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var model APIShieldOperationModel + resp.Diagnostics.Append(req.State.Get(ctx, &model)...) + if resp.Diagnostics.HasError() { + return + } + + op, err := r.client.V1.GetAPIShieldOperation( + ctx, + cloudflare.ZoneIdentifier(model.ZoneID.ValueString()), + cloudflare.GetAPIShieldOperationParams{ + OperationID: model.ID.ValueString(), + }) + + if err != nil { + resp.Diagnostics.AddError("Error reading API Shield Operation", err.Error()) + return + } + + model.ID = flatteners.String(op.ID) + model.Method = flatteners.String(op.Method) + model.Host = flatteners.String(op.Host) + model.Endpoint = NewEndpointValue(op.Endpoint) + + resp.Diagnostics.Append(resp.State.Set(ctx, &model)...) +} + +func (r *APIShieldOperationResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + resp.Diagnostics.AddError( + "Update API shield Operation is not supported", + "Update should never have been called because the resource is configured to be replaced instead", + ) +} + +func (r *APIShieldOperationResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var model APIShieldOperationModel + resp.Diagnostics.Append(req.State.Get(ctx, &model)...) + if resp.Diagnostics.HasError() { + return + } + + err := r.client.V1.DeleteAPIShieldOperation( + ctx, + cloudflare.ZoneIdentifier(model.ZoneID.ValueString()), + cloudflare.DeleteAPIShieldOperationParams{ + OperationID: model.ID.ValueString(), + }, + ) + + if err != nil { + resp.Diagnostics.AddError("Error deleting API Shield Operation", err.Error()) + return + } +} + +func (r *APIShieldOperationResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + idparts := strings.Split(req.ID, "/") + if len(idparts) != 2 { + resp.Diagnostics.AddError("error importing api_shield_operation", `invalid ID specified. Please specify the ID as "/"`) + 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], + )...) +} diff --git a/internal/sdkv2provider/resource_cloudflare_api_shield_operation_test.go b/internal/framework/service/api_shield_operation/resource_test.go similarity index 59% rename from internal/sdkv2provider/resource_cloudflare_api_shield_operation_test.go rename to internal/framework/service/api_shield_operation/resource_test.go index 5ff339386a..c4ad9eb125 100644 --- a/internal/sdkv2provider/resource_cloudflare_api_shield_operation_test.go +++ b/internal/framework/service/api_shield_operation/resource_test.go @@ -1,4 +1,4 @@ -package sdkv2provider +package apishieldoperation_test import ( "context" @@ -7,36 +7,72 @@ import ( "os" "testing" - "github.com/cloudflare/cloudflare-go" + cloudflare "github.com/cloudflare/cloudflare-go" + "github.com/cloudflare/terraform-provider-cloudflare/internal/acctest" "github.com/cloudflare/terraform-provider-cloudflare/internal/consts" + "github.com/cloudflare/terraform-provider-cloudflare/internal/utils" + "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" ) -func TestAccCloudflareAPIShieldOperation_Create(t *testing.T) { - // Temporarily unset CLOUDFLARE_API_TOKEN if it is set as the API token - // endpoint does not yet support the API tokens without an explicit scope. - if os.Getenv("CLOUDFLARE_API_TOKEN") != "" { - t.Setenv("CLOUDFLARE_API_TOKEN", "") +func init() { + resource.AddTestSweepers("cloudflare_api_shield_operation", &resource.Sweeper{ + Name: "cloudflare_api_shield_operation", + F: testSweepCloudflareCloudAPIShieldOperations, + }) +} + +func testSweepCloudflareCloudAPIShieldOperations(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)) + } + + zone := os.Getenv("CLOUDFLARE_ZONE_ID") + if zone == "" { + return errors.New("CLOUDFLARE_ZONE_ID must be set") + } + + operations, _, err := client.ListAPIShieldOperations(ctx, cloudflare.ZoneIdentifier(zone), cloudflare.ListAPIShieldOperationsParams{}) + if err != nil { + tflog.Error(ctx, fmt.Sprintf("Failed to list Cloudflare API Shield Operations: %s", err)) + } + + for _, operation := range operations { + if err := client.DeleteAPIShieldOperation( + ctx, + cloudflare.ZoneIdentifier(zone), + cloudflare.DeleteAPIShieldOperationParams{OperationID: operation.ID}, + ); err != nil { + tflog.Error(ctx, fmt.Sprintf("Failed to delete Cloudflare API Shield Operation: %s", err)) + } } - rnd := generateRandomResourceName() + return nil +} + +func TestAccCloudflareAPIShieldOperation_Create(t *testing.T) { + rnd := utils.GenerateRandomResourceName() resourceID := "cloudflare_api_shield_operation." + rnd zoneID := os.Getenv("CLOUDFLARE_ZONE_ID") domain := os.Getenv("CLOUDFLARE_DOMAIN") resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: providerFactories, - CheckDestroy: testAccCheckAPIShieldOperationDelete, + PreCheck: func() { + acctest.TestAccPreCheck(t) + }, + CheckDestroy: testAccCheckAPIShieldOperationDelete, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { - Config: testAccCloudflareAPIShieldOperation(rnd, zoneID, cloudflare.APIShieldBasicOperation{Method: "GET", Host: domain, Endpoint: "/example/path"}), + Config: testAccCloudflareAPIShieldOperation(rnd, zoneID, cloudflare.APIShieldBasicOperation{Method: "GET", Host: domain, Endpoint: "/example/path/foo/{fooId}"}), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceID, consts.ZoneIDSchemaKey, zoneID), resource.TestCheckResourceAttr(resourceID, "method", "GET"), resource.TestCheckResourceAttr(resourceID, "host", domain), - resource.TestCheckResourceAttr(resourceID, "endpoint", "/example/path"), + resource.TestCheckResourceAttr(resourceID, "endpoint", "/example/path/foo/{fooId}"), ), }, }, @@ -44,38 +80,34 @@ func TestAccCloudflareAPIShieldOperation_Create(t *testing.T) { } func TestAccCloudflareAPIShieldOperation_ForceNew(t *testing.T) { - // Temporarily unset CLOUDFLARE_API_TOKEN if it is set as the API token - // endpoint does not yet support the API tokens without an explicit scope. - if os.Getenv("CLOUDFLARE_API_TOKEN") != "" { - t.Setenv("CLOUDFLARE_API_TOKEN", "") - } - - rnd := generateRandomResourceName() + rnd := utils.GenerateRandomResourceName() resourceID := "cloudflare_api_shield_operation." + rnd zoneID := os.Getenv("CLOUDFLARE_ZONE_ID") domain := os.Getenv("CLOUDFLARE_DOMAIN") resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: providerFactories, - CheckDestroy: testAccCheckAPIShieldOperationDelete, + PreCheck: func() { + acctest.TestAccPreCheck(t) + }, + CheckDestroy: testAccCheckAPIShieldOperationDelete, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { - Config: testAccCloudflareAPIShieldOperation(rnd, zoneID, cloudflare.APIShieldBasicOperation{Method: "GET", Host: domain, Endpoint: "/example/path"}), + Config: testAccCloudflareAPIShieldOperation(rnd, zoneID, cloudflare.APIShieldBasicOperation{Method: "GET", Host: domain, Endpoint: "/example/path/foo/{fooId}"}), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceID, consts.ZoneIDSchemaKey, zoneID), resource.TestCheckResourceAttr(resourceID, "method", "GET"), resource.TestCheckResourceAttr(resourceID, "host", domain), - resource.TestCheckResourceAttr(resourceID, "endpoint", "/example/path"), + resource.TestCheckResourceAttr(resourceID, "endpoint", "/example/path/foo/{fooId}"), ), }, { - Config: testAccCloudflareAPIShieldOperation(rnd, zoneID, cloudflare.APIShieldBasicOperation{Method: "POST", Host: domain, Endpoint: "/example/path"}), + Config: testAccCloudflareAPIShieldOperation(rnd, zoneID, cloudflare.APIShieldBasicOperation{Method: "POST", Host: domain, Endpoint: "/example/path/foo/{fooId}"}), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceID, consts.ZoneIDSchemaKey, zoneID), resource.TestCheckResourceAttr(resourceID, "method", "POST"), // check that we've 'updated' the value resource.TestCheckResourceAttr(resourceID, "host", domain), - resource.TestCheckResourceAttr(resourceID, "endpoint", "/example/path"), + resource.TestCheckResourceAttr(resourceID, "endpoint", "/example/path/foo/{fooId}"), ), }, }, @@ -83,8 +115,11 @@ func TestAccCloudflareAPIShieldOperation_ForceNew(t *testing.T) { } func testAccCheckAPIShieldOperationDelete(s *terraform.State) error { - client := testAccProvider.Meta().(*cloudflare.API) - + client, err := acctest.SharedV1Client() + if err != nil { + tflog.Error(context.Background(), fmt.Sprintf("Failed to create Cloudflare client: %s", err)) + return err + } for _, rs := range s.RootModule().Resources { if rs.Type != "cloudflare_api_shield_operation" { continue diff --git a/internal/framework/service/api_shield_operation/schema.go b/internal/framework/service/api_shield_operation/schema.go new file mode 100644 index 0000000000..7438c438a8 --- /dev/null +++ b/internal/framework/service/api_shield_operation/schema.go @@ -0,0 +1,71 @@ +package apishieldoperation + +import ( + "context" + "net/http" + + "github.com/cloudflare/terraform-provider-cloudflare/internal/consts" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "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" +) + +func (r *APIShieldOperationResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Api shield operation", + Attributes: map[string]schema.Attribute{ + consts.IDSchemaKey: schema.StringAttribute{ + Description: consts.IDSchemaDescription, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + consts.ZoneIDSchemaKey: schema.StringAttribute{ + Description: consts.ZoneIDSchemaDescription, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "method": schema.StringAttribute{ + Description: "The HTTP method used to access the endpoint", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.OneOf( + http.MethodGet, + http.MethodHead, + http.MethodPost, + http.MethodPut, + http.MethodPatch, + http.MethodDelete, + http.MethodConnect, + http.MethodOptions, + http.MethodTrace, + ), + }, + }, + "host": schema.StringAttribute{ + Description: "RFC3986-compliant host", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "endpoint": schema.StringAttribute{ + Description: "The endpoint which can contain path parameter templates in curly braces, each will be replaced from left to right with `{varN}`, starting with `{var1}`. This will then be [Cloudflare-normalized](https://developers.cloudflare.com/rules/normalization/how-it-works/)", + Required: true, + CustomType: EndpointType{}, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + } +} diff --git a/internal/sdkv2provider/provider.go b/internal/sdkv2provider/provider.go index e5acdea7ab..0a43dcb207 100644 --- a/internal/sdkv2provider/provider.go +++ b/internal/sdkv2provider/provider.go @@ -21,7 +21,7 @@ import ( const ( MAXIMUM_NUMBER_OF_ENTITIES_REACHED_SUMMARY = "You've attempted to add a new %[1]s to the `terraform-plugin-sdkv2` which is no longer considered suitable for use." MAXIMUM_NUMBER_OF_ENTITIES_REACHED_DETAIL = "Due the number of known internal issues with `terraform-plugin-sdkv2` (most notably handling of zero values), we are no longer recommending using it and instead, advise using `terraform-plugin-framework` exclusively. If you must use terraform-plugin-sdkv2 for this new %[1]s you should first discuss it with a maintainer to fully understand the impact and potential ramifications. Only then should you bump %[2]s to include your %[1]s." - MAXIMUM_ALLOWED_SDKV2_RESOURCES = 145 + MAXIMUM_ALLOWED_SDKV2_RESOURCES = 144 MAXIMUM_ALLOWED_SDKV2_DATASOURCES = 23 ) @@ -219,7 +219,6 @@ func New(version string) func() *schema.Provider { "cloudflare_account": resourceCloudflareAccount(), "cloudflare_address_map": resourceCloudflareAddressMap(), "cloudflare_api_shield": resourceCloudflareAPIShield(), - "cloudflare_api_shield_operation": resourceCloudflareAPIShieldOperation(), "cloudflare_api_shield_operation_schema_validation_settings": resourceCloudflareAPIShieldOperationSchemaValidationSettings(), "cloudflare_api_shield_schema": resourceCloudflareAPIShieldSchemas(), "cloudflare_api_shield_schema_validation_settings": resourceCloudflareAPIShieldSchemaValidationSettings(), diff --git a/internal/sdkv2provider/resource_cloudflare_api_shield_operation.go b/internal/sdkv2provider/resource_cloudflare_api_shield_operation.go deleted file mode 100644 index 8f81ebc43d..0000000000 --- a/internal/sdkv2provider/resource_cloudflare_api_shield_operation.go +++ /dev/null @@ -1,109 +0,0 @@ -package sdkv2provider - -import ( - "context" - "fmt" - - "github.com/pkg/errors" - - "github.com/MakeNowJust/heredoc/v2" - "github.com/cloudflare/cloudflare-go" - "github.com/cloudflare/terraform-provider-cloudflare/internal/consts" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -func resourceCloudflareAPIShieldOperation() *schema.Resource { - return &schema.Resource{ - Schema: resourceCloudflareAPIShieldOperationSchema(), - CreateContext: resourceCloudflareAPIShieldOperationCreate, - ReadContext: resourceCloudflareAPIShieldOperationRead, - DeleteContext: resourceCloudflareAPIShieldOperationDelete, - Importer: &schema.ResourceImporter{ - StateContext: nil, - }, - Description: heredoc.Doc(` - Provides a resource to manage an operation in API Shield Endpoint Management. - `), - } -} - -func resourceCloudflareAPIShieldOperationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*cloudflare.API) - zoneID := d.Get(consts.ZoneIDSchemaKey).(string) - - ops, err := client.CreateAPIShieldOperations( - ctx, - cloudflare.ZoneIdentifier(zoneID), - cloudflare.CreateAPIShieldOperationsParams{ - Operations: []cloudflare.APIShieldBasicOperation{ - { - Method: d.Get("method").(string), - Host: d.Get("host").(string), - Endpoint: d.Get("endpoint").(string), - }, - }, - }, - ) - - if err != nil { - return diag.FromErr(errors.Wrap(err, "failed to create API Shield Operation")) - } - - if length := len(ops); length != 1 { - return diag.FromErr(fmt.Errorf("expected output to have 1 entry but got: %d", length)) - } - - d.SetId(ops[0].ID) - return resourceCloudflareAPIShieldOperationRead(ctx, d, meta) -} - -func resourceCloudflareAPIShieldOperationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*cloudflare.API) - zoneID := d.Get(consts.ZoneIDSchemaKey).(string) - - op, err := client.GetAPIShieldOperation( - ctx, - cloudflare.ZoneIdentifier(zoneID), - cloudflare.GetAPIShieldOperationParams{ - OperationID: d.Id(), - }, - ) - - if err != nil { - return diag.FromErr(fmt.Errorf("failed to fetch API Shield Operation: %w", err)) - } - - if err := d.Set("method", op.Method); err != nil { - return diag.FromErr(err) - } - - if err := d.Set("host", op.Host); err != nil { - return diag.FromErr(err) - } - - if err := d.Set("endpoint", op.Endpoint); err != nil { - return diag.FromErr(err) - } - - d.SetId(op.ID) - return nil -} - -func resourceCloudflareAPIShieldOperationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*cloudflare.API) - zoneID := d.Get(consts.ZoneIDSchemaKey).(string) - - err := client.DeleteAPIShieldOperation( - ctx, - cloudflare.ZoneIdentifier(zoneID), - cloudflare.DeleteAPIShieldOperationParams{ - OperationID: d.Id(), - }, - ) - if err != nil { - return diag.FromErr(fmt.Errorf("failed to fetch API Shield Operation: %w", err)) - } - - return nil -} diff --git a/internal/sdkv2provider/schema_cloudflare_api_shield_operation.go b/internal/sdkv2provider/schema_cloudflare_api_shield_operation.go deleted file mode 100644 index c7768ce5bc..0000000000 --- a/internal/sdkv2provider/schema_cloudflare_api_shield_operation.go +++ /dev/null @@ -1,37 +0,0 @@ -package sdkv2provider - -import ( - "github.com/MakeNowJust/heredoc/v2" - - "github.com/cloudflare/terraform-provider-cloudflare/internal/consts" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -func resourceCloudflareAPIShieldOperationSchema() map[string]*schema.Schema { - return map[string]*schema.Schema{ - consts.ZoneIDSchemaKey: { - Description: consts.ZoneIDSchemaDescription, - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - "method": { - Description: "The HTTP method used to access the endpoint", - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - "host": { - Description: "RFC3986-compliant host", - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - "endpoint": { - Description: heredoc.Doc("The endpoint which can contain path parameter templates in curly braces, each will be replaced from left to right with `{varN}`, starting with `{var1}`. This will then be [Cloudflare-normalized](https://developers.cloudflare.com/rules/normalization/how-it-works/)"), - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - } -}