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

Adds support for D1 #2850

Merged
merged 5 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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/2850.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
resource/d1: Adds support for D1 databases
jacobbednarz marked this conversation as resolved.
Show resolved Hide resolved
```
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"`
}
81 changes: 81 additions & 0 deletions internal/framework/service/d1/resouce_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)
}
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],
)...)
}
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") }}