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 new resource for dex tests #2250

Merged
merged 11 commits into from
Mar 23, 2023
3 changes: 3 additions & 0 deletions .changelog/2250.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
cloudflare_device_dex_tests
```
66 changes: 66 additions & 0 deletions docs/resources/device_dex_tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
page_title: "cloudflare_device_dex_tests Resource - Cloudflare"
subcategory: ""
description: |-
Provides a Cloudflare Device Digital Experience Monitoring (DEX) test configuration resource.
---

# cloudflare_device_dex_tests (Resource)

Provides a Cloudflare Device Digital Experience Monitoring (DEX) test configuration resource.

## Example Usage

```terraform
resource "cloudflare_device_dex_tests" "dex_tests" {
test_id = "f174e90a-fafe-4643-bbbc-4a0ed4fc8415",
name = "GET dashboard",
description = "Send a HTTP GET request to the 'home' endpoint of the dash every half hour.",
interval = "0h30m0s",
enabled = true,
data = {
host = "https://dash.cloudflare.com/home",
kind = "http",
method = "GET"
}
}
```
<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `test_id` (String) The account identifier to target for the resource.
- `name` (String) The name of the DEX test. Must be unique.
- `interval` (String) How often the test will run. An interval of 2 hours, 45 minutes, and 30 seconds would be "2h45m30s".
- `enabled` (Boolean) `true` if test is enabled to run.
- `data` (Block List, Min: 1, Max: 1) The configuration object which contains the details for the WARP client to conduct the test. (see [below for nested schema](#nestedblock--config))

### Optional

- `description` (String) Additional details about the test.

### Read-Only

- `test_id` (String) The ID of this resource.

<a id="nestedblock--config"></a>
### Nested Schema for `config`

Required:
- `kind` (String) The type of request the WARP client will make to the specified host. Available values: `http`, `traceroute`.

`http` kind required fields:
- `host` (String) A valid URL.
- `method` (String) HTTP request method. Available values: `GET`.

`traceroute` kind required fields:
- `host` (String) A valid hostname or IP address.

## Import

Import is supported using the following syntax:

```shell
$ terraform import cloudflare_device_dex_tests.example <account_id>/<device_dex_tests_test_id>
```
1 change: 1 addition & 0 deletions examples/resources/cloudflare_device_dex_tests/import.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
$ terraform import cloudflare_device_dex_tests.example <account_id>/<device_dex_tests_id>
12 changes: 12 additions & 0 deletions examples/resources/cloudflare_device_dex_tests/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
resource "cloudflare_device_dex_tests" "dex_tests" {
test_id = "f174e90a-fafe-4643-bbbc-4a0ed4fc8415"
name = "GET dashboard"
description = "Send a HTTP GET request to the 'home' endpoint of the dash every half hour."
interval = "0h30m0s"
enabled = true
data {
host = "https://dash.cloudflare.com/home"
kind = "http"
method = "GET"
}
}
1 change: 1 addition & 0 deletions internal/sdkv2provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ func New(version string) func() *schema.Provider {
"cloudflare_custom_hostname": resourceCloudflareCustomHostname(),
"cloudflare_custom_pages": resourceCloudflareCustomPages(),
"cloudflare_custom_ssl": resourceCloudflareCustomSsl(),
"cloudflare_device_dex_tests": resourceCloudflareDeviceDexTests(),
"cloudflare_device_managed_networks": resourceCloudflareDeviceManagedNetworks(),
"cloudflare_device_policy_certificates": resourceCloudflareDevicePolicyCertificates(),
"cloudflare_device_posture_integration": resourceCloudflareDevicePostureIntegration(),
Expand Down
177 changes: 177 additions & 0 deletions internal/sdkv2provider/resource_cloudflare_device_dex_tests.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package sdkv2provider

import (
"context"
"errors"
"fmt"
"strings"

"github.com/cloudflare/cloudflare-go"
"github.com/cloudflare/terraform-provider-cloudflare/internal/consts"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func resourceCloudflareDeviceDexTests() *schema.Resource {
return &schema.Resource{
Schema: resourceCloudflareDeviceDexTestsSchema(),
CreateContext: resourceCloudflareDeviceDexTestsCreate,
ReadContext: resourceCloudflareDeviceDexTestsRead,
UpdateContext: resourceCloudflareDeviceDexTestsUpdate,
DeleteContext: resourceCloudflareDeviceDexTestsDelete,
Importer: &schema.ResourceImporter{
StateContext: resourceCloudflareDeviceDexTestsImport,
},
Description: "Provides a Cloudflare Device Dex Test resource. Device Dex Tests allow for building location-aware device settings policies.",
}
}

func resourceCloudflareDeviceDexTestsRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*cloudflare.API)
identifier := cloudflare.AccountIdentifier(d.Get(consts.AccountIDSchemaKey).(string))
tflog.Debug(ctx, fmt.Sprintf("Reading Cloudflare Device Dex Test for Id: %+v", d.Id()))

dexTest, err := client.GetDeviceDexTest(ctx, identifier, d.Id())

var notFoundError *cloudflare.NotFoundError
if errors.As(err, &notFoundError) {
tflog.Info(ctx, fmt.Sprintf("Device Dex Test %s no longer exists", d.Id()))
d.SetId("")
return nil
}
if err != nil {
return diag.FromErr(fmt.Errorf("error reading Device Dex Test: %w", err))
}

d.Set("name", dexTest.Name)
d.Set("description", dexTest.Description)
d.Set("enabled", dexTest.Enabled)
d.Set("interval", dexTest.Interval)
d.Set("updated", dexTest.Updated)
d.Set("created", dexTest.Created)
d.Set("data", convertDeviceDexTestDataToSchema(dexTest.Data))

return nil
}

func resourceCloudflareDeviceDexTestsCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*cloudflare.API)
identifier := cloudflare.AccountIdentifier(d.Get(consts.AccountIDSchemaKey).(string))

kind := d.Get("config.0.kind").(string)

params := cloudflare.CreateDeviceDexTestParams{
Name: d.Get("name").(string),
Description: d.Get("type").(string),
Interval: d.Get("type").(string),
Enabled: d.Get("type").(bool),
Data: &cloudflare.DeviceDexTestData{
"Kind": kind,
"Host": d.Get("config.0.host").(string),
},
}

if kind == "http" {
(*params.Data)["Method"] = d.Get("config.0.method").(string)
}

tflog.Debug(ctx, fmt.Sprintf("Creating Cloudflare Device Dex Test with params: %+v", params))

dexTest, err := client.CreateDeviceDexTest(ctx, identifier, params)

if err != nil {
return diag.FromErr(fmt.Errorf("error creating Device Dex Test with provided config: %w", err))
}

d.SetId(dexTest.TestID)

return resourceCloudflareDeviceDexTestsRead(ctx, d, meta)
}

func resourceCloudflareDeviceDexTestsUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*cloudflare.API)
identifier := cloudflare.AccountIdentifier(d.Get(consts.AccountIDSchemaKey).(string))

updatedDeviceDexTestParams := cloudflare.UpdateDeviceDexTestParams{
TestID: d.Id(),
Name: d.Get("name").(string),
Description: d.Get("description").(string),
Interval: d.Get("interval").(string),
Enabled: d.Get("enabled").(bool),
Data: &cloudflare.DeviceDexTestData{
"Kind": d.Get("config.0.kind").(string),
"Host": d.Get("config.0.host").(string),
"Method": d.Get("config.0.method").(string),
},
}

tflog.Debug(ctx, fmt.Sprintf("Updating Cloudflare Device Dex Test with params: %+v", updatedDeviceDexTestParams))

dexTest, err := client.UpdateDeviceDexTest(ctx, identifier, updatedDeviceDexTestParams)

if err != nil {
return diag.FromErr(fmt.Errorf("error updating Device Dex Test for ID %q: %w", d.Id(), err))
}
if dexTest.TestID == "" {
return diag.FromErr(fmt.Errorf("failed to find Dex Test ID in update response; resource was empty"))
}

return resourceCloudflareDeviceDexTestsRead(ctx, d, meta)
}

func resourceCloudflareDeviceDexTestsDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*cloudflare.API)
identifier := cloudflare.AccountIdentifier(d.Get(consts.AccountIDSchemaKey).(string))
tflog.Debug(ctx, fmt.Sprintf("Deleting Cloudflare Device Dex Test using ID: %s", d.Id()))

if _, err := client.DeleteDexTest(ctx, identifier, d.Id()); err != nil {
return diag.FromErr(fmt.Errorf("error deleting DLP Profile for ID %q: %w", d.Id(), err))
}

resourceCloudflareDeviceDexTestsRead(ctx, d, meta)
return nil
}

func resourceCloudflareDeviceDexTestsImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
accountID, dexTestID, err := parseDeviceDexTestsIDImport(d.Id())
if err != nil {
return nil, err
}

tflog.Debug(ctx, fmt.Sprintf("Importing Cloudflare Device Dex Test: id %s for account %s", dexTestID, accountID))

d.Set("account_id", accountID)
d.SetId(dexTestID)

resourceCloudflareDeviceDexTestsRead(ctx, d, meta)

return []*schema.ResourceData{d}, nil
}

func parseDeviceDexTestsIDImport(id string) (string, string, error) {
attributes := strings.SplitN(id, "/", 2)

if len(attributes) != 2 {
return "", "", fmt.Errorf("invalid id (\"%s\") specified, should be in format \"accountID/testID\"", id)
}

return attributes[0], attributes[1], nil
}

func convertDeviceDexTestDataToSchema(input *cloudflare.DeviceDexTestData) []interface{} {
kind, _ := (*input)["Kind"]
host, _ := (*input)["Host"]
method, _ := (*input)["Method"]

m := map[string]interface{}{
"kind": kind,
"host": host,
}

// The `method` field doesn't exist under all conditions. Only add it if we are converting an `http` kind of test.
if kind == "http" && method != "" {
m["method"] = method
}
return []interface{}{m}
}
108 changes: 108 additions & 0 deletions internal/sdkv2provider/resource_cloudflare_device_dex_tests_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package sdkv2provider

import (
"fmt"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

func TestAccCloudflareDeviceDexTests(t *testing.T) {
rnd := generateRandomResourceName()
name := fmt.Sprintf("cloudflare_device_dex_tests.%s", rnd)

resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheckAccount(t)
},
ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
Config: testAccCloudflareDeviceDexTestsHttp(accountID, rnd),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(name, "account_id", accountID),
resource.TestCheckResourceAttr(name, "name", rnd),
resource.TestCheckResourceAttr(name, "description", rnd),
resource.TestCheckResourceAttr(name, "interval", "0h30m0s"),
resource.TestCheckResourceAttr(name, "enabled", "true"),
resource.TestCheckResourceAttr(name, "data.0.host", "https://dash.cloudflare.com/home"),
resource.TestCheckResourceAttr(name, "data.0.kind", "http"),
resource.TestCheckResourceAttr(name, "data.0.method", "GET"),
),
},
{
Config: testAccCloudflareDeviceDexTestsTraceroute(accountID, rnd),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(name, "account_id", accountID),
resource.TestCheckResourceAttr(name, "name", rnd),
resource.TestCheckResourceAttr(name, "description", rnd),
resource.TestCheckResourceAttr(name, "interval", "0h30m0s"),
resource.TestCheckResourceAttr(name, "enabled", "true"),
resource.TestCheckResourceAttr(name, "data.0.host", "dash.cloudflare.com"),
resource.TestCheckResourceAttr(name, "data.0.kind", "http"),
),
},
{
Config: testAccCloudflareDeviceDexTestsTracerouteIpv4(accountID, rnd),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(name, "account_id", accountID),
resource.TestCheckResourceAttr(name, "name", rnd),
resource.TestCheckResourceAttr(name, "description", rnd),
resource.TestCheckResourceAttr(name, "interval", "0h30m0s"),
resource.TestCheckResourceAttr(name, "enabled", "true"),
resource.TestCheckResourceAttr(name, "data.0.host", "1.1.1.1"),
resource.TestCheckResourceAttr(name, "data.0.kind", "http"),
),
},
},
})
}

func testAccCloudflareDeviceDexTestsHttp(accountID, rnd string) string {
return fmt.Sprintf(`
resource "cloudflare_device_dex_tests" "%[1]s" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these tests are all missing the required account_id field.

test_id = "%[2]s"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_id doesn't appear in your schema. is this the value we are using for id? if so, we don't need it here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes for this resource we used test_id to represent what would usually be the id field. Thanks for the heads up.

name = "%[1]s"
description = "%[1]s"
interval = "0h30m0s"
enabled = true
data {
host = "https://dash.cloudflare.com/home"
kind = "http"
method = "GET"
}
}
`, rnd, accountID)
}

func testAccCloudflareDeviceDexTestsTraceroute(accountID, rnd string) string {
return fmt.Sprintf(`
resource "cloudflare_device_dex_tests" "%[1]s" {
test_id = "%[2]s"
name = "%[1]s"
description = "%[1]s"
interval = "0h30m0s"
enabled = true
data {
host = "dash.cloudflare.com"
kind = "traceroute"
}
}
`, rnd, accountID)
}

func testAccCloudflareDeviceDexTestsTracerouteIpv4(accountID, rnd string) string {
return fmt.Sprintf(`
resource "cloudflare_device_dex_tests" "%[1]s" {
test_id = "%[2]s"
name = "%[1]s"
description = "%[1]s"
interval = "0h30m0s"
enabled = true
data {
host = "1.1.1.1"
kind = "traceroute"
}
}
`, rnd, accountID)
}
Loading