Skip to content

Commit

Permalink
Merge pull request #2850 from Cyb3r-Jak3/d1
Browse files Browse the repository at this point in the history
Adds support for D1
  • Loading branch information
jacobbednarz authored Oct 16, 2023
2 parents 4bcd120 + 0933753 commit 5e3368e
Show file tree
Hide file tree
Showing 10 changed files with 362 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .changelog/2850.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
cloudflare_d1_database
```
45 changes: 45 additions & 0 deletions docs/resources/d1_database.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
page_title: "cloudflare_d1_database Resource - Cloudflare"
subcategory: ""
description: |-
The D1 Database https://developers.cloudflare.com/d1/ resource allows you to manage Cloudflare D1 databases.
---

# cloudflare_d1_database (Resource)

The [D1 Database](https://developers.cloudflare.com/d1/) resource allows you to manage Cloudflare D1 databases.

!> When a D1 Database is replaced all the data is lost. Please ensure you have a backup of your data before replacing a D1 Database.


## Example Usage

```terraform
resource "cloudflare_d1_database" "example" {
account_id = "f037e56e89293a057740de681ac9abbe"
name = "terraform-database"
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `account_id` (String) The account identifier to target for the resource.
- `name` (String) The name of the D1 Database.

### Read-Only

- `id` (String) The identifier of this resource.
- `version` (String) The backend version of the database.

## Import


Import is supported using the following syntax:

```shell
$ terraform import cloudflare_d1_database.example <account id>/<database id>
```

1 change: 1 addition & 0 deletions examples/resources/cloudflare_d1_database/import.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
$ terraform import cloudflare_d1_database.example <account id>/<database id>
4 changes: 4 additions & 0 deletions examples/resources/cloudflare_d1_database/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
resource "cloudflare_d1_database" "example" {
account_id = "f037e56e89293a057740de681ac9abbe"
name = "terraform-database"
}
2 changes: 2 additions & 0 deletions internal/framework/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/cloudflare/cloudflare-go"
"github.com/cloudflare/terraform-provider-cloudflare/internal/consts"
"github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/api_token_permissions_groups"
"github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/d1"
"github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/r2_bucket"
"github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/rulesets"
"github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/turnstile"
Expand Down Expand Up @@ -328,6 +329,7 @@ func (p *CloudflareProvider) Resources(ctx context.Context) []func() resource.Re
r2_bucket.NewResource,
rulesets.NewResource,
turnstile.NewResource,
d1.NewResource,
}
}

Expand Down
10 changes: 10 additions & 0 deletions internal/framework/service/d1/model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package d1

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

type DatabaseModel struct {
AccountID types.String `tfsdk:"account_id"`
Name types.String `tfsdk:"name"`
ID types.String `tfsdk:"id"`
Version types.String `tfsdk:"version"`
}
135 changes: 135 additions & 0 deletions internal/framework/service/d1/resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package d1

import (
"context"
"fmt"
"strings"

"github.com/cloudflare/cloudflare-go"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
)

// Ensure provider defined types fully satisfy framework interfaces.
var _ resource.Resource = &DatabaseResource{}
var _ resource.ResourceWithImportState = &DatabaseResource{}

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

// DatabaseResource defines the resource implementation.
type DatabaseResource struct {
client *cloudflare.API
}

func (r *DatabaseResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_d1_database"
}

func (r *DatabaseResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}

client, ok := req.ProviderData.(*cloudflare.API)

if !ok {
resp.Diagnostics.AddError(
"unexpected resource configure type",
fmt.Sprintf("Expected *cloudflare.API, got: %T. Please report this issue to the provider developers.", req.ProviderData),
)

return
}

r.client = client
}

func (r *DatabaseResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data *DatabaseModel

resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)

if resp.Diagnostics.HasError() {
return
}

database, err := r.client.CreateD1Database(ctx, cloudflare.AccountIdentifier(data.AccountID.ValueString()),
cloudflare.CreateD1DatabaseParams{
Name: data.Name.ValueString(),
},
)
if err != nil {
resp.Diagnostics.AddError("failed to create D1 database", err.Error())
return
}
data.ID = types.StringValue(database.UUID)
data.Name = types.StringValue(database.Name)
data.Version = types.StringValue(database.Version)
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

func (r *DatabaseResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var data *DatabaseModel

resp.Diagnostics.Append(req.State.Get(ctx, &data)...)

if resp.Diagnostics.HasError() {
return
}

database, err := r.client.GetD1Database(ctx, cloudflare.AccountIdentifier(data.AccountID.ValueString()), data.ID.ValueString())
if err != nil {
resp.Diagnostics.AddError("failed reading D1 database", err.Error())
return
}
data.ID = types.StringValue(database.UUID)
data.Name = types.StringValue(database.Name)
data.Version = types.StringValue(database.Version)
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

func (r *DatabaseResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var data *DatabaseModel

resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)

if resp.Diagnostics.HasError() {
return
}

resp.Diagnostics.AddError("failed to update D1 database", "Not implemented")
}

func (r *DatabaseResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var data *DatabaseModel

resp.Diagnostics.Append(req.State.Get(ctx, &data)...)

if resp.Diagnostics.HasError() {
return
}

err := r.client.DeleteD1Database(ctx, cloudflare.AccountIdentifier(data.AccountID.ValueString()), data.ID.ValueString())

if err != nil {
resp.Diagnostics.AddError("failed to delete D1 database", err.Error())
return
}
}

func (r *DatabaseResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
idparts := strings.Split(req.ID, "/")
if len(idparts) != 2 {
resp.Diagnostics.AddError("error importing D1 database", "invalid ID specified. Please specify the ID as \"account_id/name\"")
return
}
resp.Diagnostics.Append(resp.State.SetAttribute(
ctx, path.Root("account_id"), idparts[0],
)...)
resp.Diagnostics.Append(resp.State.SetAttribute(
ctx, path.Root("id"), idparts[1],
)...)
}
81 changes: 81 additions & 0 deletions internal/framework/service/d1/resource_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package d1_test

import (
"context"
"fmt"
"os"
"testing"

"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-testing/helper/resource"
)

func TestMain(m *testing.M) {
resource.TestMain(m)
}

func init() {
resource.AddTestSweepers("cloudflare_d1_database", &resource.Sweeper{
Name: "cloudflare_d1_database",
F: func(region string) error {
client, err := acctest.SharedClient()
accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID")

if err != nil {
return fmt.Errorf("error establishing client: %w", err)
}

ctx := context.Background()
databases, _, err := client.ListD1Databases(ctx, cloudflare.AccountIdentifier(accountID), cloudflare.ListD1DatabasesParams{})
if err != nil {
return fmt.Errorf("failed to fetch R2 buckets: %w", err)
}

for _, database := range databases {
err := client.DeleteD1Database(ctx, cloudflare.AccountIdentifier(accountID), database.UUID)
if err != nil {
return fmt.Errorf("failed to delete D1 database %q: %w", database.Name, err)
}
}

return nil
},
})
}

func TestAccCloudflareD1Database_Basic(t *testing.T) {
rnd := utils.GenerateRandomResourceName()
accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID")
resourceName := "cloudflare_d1_database." + rnd

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.TestAccPreCheck(t) },
ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
Config: testAccCheckCloudflareD1DatabaseBasic(rnd, accountID),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "name", rnd),
resource.TestCheckResourceAttrSet(resourceName, "id"),
resource.TestCheckResourceAttr(resourceName, "version", "beta"),
),
},
{
ResourceName: resourceName,
ImportStateIdPrefix: fmt.Sprintf("%s/", accountID),
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func testAccCheckCloudflareD1DatabaseBasic(rnd, accountID string) string {
return fmt.Sprintf(`
resource "cloudflare_d1_database" "%[1]s" {
account_id = "%[2]s"
name = "%[1]s"
}`, rnd, accountID)
}
54 changes: 54 additions & 0 deletions internal/framework/service/d1/schema.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package d1

import (
"context"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"regexp"

"github.com/MakeNowJust/heredoc/v2"
"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"
)

func (r *DatabaseResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: heredoc.Doc(`
The [D1 Database](https://developers.cloudflare.com/d1/) resource allows you to manage Cloudflare D1 databases.
`),

Attributes: map[string]schema.Attribute{
consts.AccountIDSchemaKey: schema.StringAttribute{
MarkdownDescription: consts.AccountIDSchemaDescription,
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
consts.IDSchemaKey: schema.StringAttribute{
MarkdownDescription: consts.IDSchemaDescription,
Computed: true,
},
"name": schema.StringAttribute{
Required: true,
MarkdownDescription: "The name of the D1 Database.",
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Validators: []validator.String{
stringvalidator.RegexMatches(
regexp.MustCompile(`^[a-z0-9][a-z0-9-_]*$`),
"must contain only lowercase alphanumeric characters",
),
},
},
"version": schema.StringAttribute{
Computed: true,
MarkdownDescription: "The backend version of the database.",
},
},
}
}
27 changes: 27 additions & 0 deletions templates/resources/d1_database.md.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
page_title: "{{.Name}} {{.Type}} - {{.RenderedProviderName}}"
subcategory: ""
description: |-
{{ .Description | plainmarkdown | trimspace | prefixlines " " }}
---

# {{.Name}} ({{.Type}})

{{ .Description | trimspace }}

!> When a D1 Database is replaced all the data is lost. Please ensure you have a backup of your data before replacing a D1 Database.


## Example Usage

{{ tffile (printf "%s%s%s" "examples/resources/" .Name "/resource.tf") }}

{{ .SchemaMarkdown | trimspace }}

## Import


Import is supported using the following syntax:

{{ codefile "shell" (printf "%s%s%s" "examples/resources/" .Name "/import.sh") }}

0 comments on commit 5e3368e

Please sign in to comment.