From 25b4a20fb0ffea8077aaa743360327b69c4ea551 Mon Sep 17 00:00:00 2001 From: joe miller Date: Wed, 15 Jan 2025 18:16:01 +0000 Subject: [PATCH] feat: add data source and resource `planetscale_branch_safe_migrations` 1. New data source `planetscale_branch_safe_migrations` 2. New resource `planetscale_branch_safe_migrations` Also: 1. Added import examples for `planetscale_branch`, `planetscale_database`, and `planetscale_password` resources. example: ```hcl provider "planetscale" {} locals { org = "my-org" db_name = "example-db" } resource "planetscale_database" "example" { name = local.db_name organization = local.org allow_data_branching = false cluster_size = "PS_10" default_branch = "main" } resource "planetscale_branch" "staging" { name = "staging" database = planetscale_database.example.name organization = planetscale_database.example.organization parent_branch = planetscale_database.example.default_branch production = false } resource "planetscale_branch_safe_migrations" "staging" { database = planetscale_database.example.name organization = planetscale_database.example.organization branch = planetscale_branch.staging.name enabled = true } data "planetscale_branch_safe_migrations" "default_branch" { database = planetscale_database.example.name organization = planetscale_database.example.organization branch = planetscale_database.example.default_branch } data "planetscale_branch_safe_migrations" "staging" { database = planetscale_database.example.name organization = planetscale_database.example.organization branch = planetscale_branch.staging.name } output "safe_migrations_default_branch" { value = data.planetscale_branch_safe_migrations.default_branch } output "safe_migrations" { value = data.planetscale_branch_safe_migrations.staging } ``` --- .golangci.yml | 2 +- GNUmakefile | 4 + docs/data-sources/branch.md | 1 + docs/data-sources/branch_safe_migrations.md | 38 ++++ docs/data-sources/branches.md | 1 + docs/resources/branch.md | 9 + docs/resources/branch_safe_migrations.md | 67 ++++++ docs/resources/database.md | 9 + docs/resources/password.md | 10 + .../data-source.tf | 9 + .../resources/planetscale_branch/import.sh | 2 + .../import.sh | 2 + .../resource.tf | 28 +++ .../resources/planetscale_database/import.sh | 2 + .../resources/planetscale_password/import.sh | 3 + .../branch_safe_migrations_data_source.go | 76 +++++++ ...branch_safe_migrations_data_source_test.go | 43 ++++ .../branch_safe_migrations_resource.go | 202 ++++++++++++++++++ .../branch_safe_migrations_resource_test.go | 92 ++++++++ internal/provider/models_data_source.go | 44 ++++ .../provider/organization_data_source_test.go | 13 +- internal/provider/provider.go | 2 + 22 files changed, 653 insertions(+), 6 deletions(-) create mode 100644 docs/data-sources/branch_safe_migrations.md create mode 100644 docs/resources/branch_safe_migrations.md create mode 100644 examples/data-sources/planetscale_branch_safe_migrations/data-source.tf create mode 100644 examples/resources/planetscale_branch/import.sh create mode 100644 examples/resources/planetscale_branch_safe_migrations/import.sh create mode 100644 examples/resources/planetscale_branch_safe_migrations/resource.tf create mode 100644 examples/resources/planetscale_database/import.sh create mode 100644 examples/resources/planetscale_password/import.sh create mode 100644 internal/provider/branch_safe_migrations_data_source.go create mode 100644 internal/provider/branch_safe_migrations_data_source_test.go create mode 100644 internal/provider/branch_safe_migrations_resource.go create mode 100644 internal/provider/branch_safe_migrations_resource_test.go diff --git a/.golangci.yml b/.golangci.yml index 223cf95..66a3313 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -24,4 +24,4 @@ linters: - unconvert - unparam - unused - - vet \ No newline at end of file + - govet diff --git a/GNUmakefile b/GNUmakefile index afdb7c3..6a3612a 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -13,6 +13,10 @@ lint: testacc: TF_ACC=1 go test -parallel=2 ./... -v $(TESTARGS) -timeout 120m +.PHONY: generate-docs +generate-docs: + go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs + .PHONY: generate generate: bash ./script/update_openapi_spec diff --git a/docs/data-sources/branch.md b/docs/data-sources/branch.md index d2d697b..5876aa8 100644 --- a/docs/data-sources/branch.md +++ b/docs/data-sources/branch.md @@ -50,6 +50,7 @@ output "branch" { - `region` (Attributes) The region in which this branch lives. (see [below for nested schema](#nestedatt--region)) - `restore_checklist_completed_at` (String) When a user last marked a backup restore checklist as completed. - `restored_from_branch` (Attributes) (see [below for nested schema](#nestedatt--restored_from_branch)) +- `safe_migrations` (Boolean) Whether safe migrations are enabled for this branch. - `schema_last_updated_at` (String) When the schema for the branch was last updated. - `shard_count` (Number) The number of shards in the branch. - `sharded` (Boolean) Whether or not the branch is sharded. diff --git a/docs/data-sources/branch_safe_migrations.md b/docs/data-sources/branch_safe_migrations.md new file mode 100644 index 0000000..fff7e79 --- /dev/null +++ b/docs/data-sources/branch_safe_migrations.md @@ -0,0 +1,38 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "planetscale_branch_safe_migrations Data Source - terraform-provider-planetscale" +subcategory: "" +description: |- + Safe migration state on a PlanetScale branch. +--- + +# planetscale_branch_safe_migrations (Data Source) + +Safe migration state on a PlanetScale branch. + +## Example Usage + +```terraform +data "planetscale_branch_safe_migrations" "example" { + organization = "example.com" + database = "example_db" + branch = "main" +} + +output "safe_migrations" { + value = data.planetscale_branch_safe_migrations.example +} +``` + + +## Schema + +### Required + +- `branch` (String) The name of the branch this safe migrations configuration belongs to. +- `database` (String) The database this branch belongs to. +- `organization` (String) The organization this branch belongs to. + +### Read-Only + +- `enabled` (Boolean) Whether safe migrations are enabled for this branch. diff --git a/docs/data-sources/branches.md b/docs/data-sources/branches.md index a946e18..fe48519 100644 --- a/docs/data-sources/branches.md +++ b/docs/data-sources/branches.md @@ -58,6 +58,7 @@ Read-Only: - `region` (Attributes) The region in which this branch lives. (see [below for nested schema](#nestedatt--branches--region)) - `restore_checklist_completed_at` (String) When a user last marked a backup restore checklist as completed. - `restored_from_branch` (Attributes) (see [below for nested schema](#nestedatt--branches--restored_from_branch)) +- `safe_migrations` (Boolean) Whether safe migrations are enabled for this branch. - `schema_last_updated_at` (String) When the schema for the branch was last updated. - `shard_count` (Number) The number of shards in the branch. - `sharded` (Boolean) Whether or not the branch is sharded. diff --git a/docs/resources/branch.md b/docs/resources/branch.md index 39c0d6a..efcb983 100644 --- a/docs/resources/branch.md +++ b/docs/resources/branch.md @@ -87,3 +87,12 @@ Read-Only: - `id` (String) The ID for the resource. - `name` (String) The name for the resource. - `updated_at` (String) When the resource was last updated. + +## Import + +Import is supported using the following syntax: + +```shell +# Branches can be imported using "org,database,branch" as the identifier. +terraform import planetscale_branch.example "org,database,branch" +``` diff --git a/docs/resources/branch_safe_migrations.md b/docs/resources/branch_safe_migrations.md new file mode 100644 index 0000000..1bbbb76 --- /dev/null +++ b/docs/resources/branch_safe_migrations.md @@ -0,0 +1,67 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "planetscale_branch_safe_migrations Resource - terraform-provider-planetscale" +subcategory: "" +description: |- + Manages safe migrations settings for a PlanetScale branch. +--- + +# planetscale_branch_safe_migrations (Resource) + +Manages safe migrations settings for a PlanetScale branch. + +## Example Usage + +```terraform +# Enable safe_migrations on default branch: +resource "planetscale_database" "example" { + organization = "example" + name = "example" + default_branch = "main" +} + +resource "planetscale_branch_safe_migrations" "main" { + organization = planetscale_branch.example.organization + database = planetscale_branch.example.database + branch = planetscale_branch.example.default_branch + enabled = true +} + +# Enable safe_migrations on a branch: +resource "planetscale_branch" "staging" { + organization = planetscale_branch.example.organization + database = planetscale_branch.example.database + parent_branch = planetscale_branch.example.default_branch + name = "staging" +} + +resource "planetscale_branch_safe_migrations" "staging" { + database = planetscale_database.example.name + organization = planetscale_database.example.organization + branch = planetscale_branch.staging.name + enabled = true +} +``` + + +## Schema + +### Required + +- `branch` (String) The name of the branch to configure safe migrations on.. +- `database` (String) The database this branch belongs to. +- `enabled` (Boolean) Whether safe migrations are enabled for this branch. +- `organization` (String) The organization this branch belongs to. + +### Read-Only + +- `id` (String) The ID of the branch. + +## Import + +Import is supported using the following syntax: + +```shell +# Safe migrations configuration can be imported using "org,database,branch" as the identifier. +terraform import planetscale_branch_safe_migrations.example "org,database,branch" +``` diff --git a/docs/resources/database.md b/docs/resources/database.md index cbe57cf..f120c77 100644 --- a/docs/resources/database.md +++ b/docs/resources/database.md @@ -90,3 +90,12 @@ Required: - `database` (String) The name of the database imported from. - `hostname` (String) The hostname where the database lives. - `port` (String) The port on which the database listens on the host. + +## Import + +Import is supported using the following syntax: + +```shell +# Databases can be imported using "org,database" as the identifier. +terraform import planetscale_branch.example "org,database" +``` diff --git a/docs/resources/password.md b/docs/resources/password.md index 3faa079..6c3337f 100644 --- a/docs/resources/password.md +++ b/docs/resources/password.md @@ -89,3 +89,13 @@ Read-Only: - `provider` (String) Provider for the region (ex. AWS). - `public_ip_addresses` (List of String) Public IP addresses for the region. - `slug` (String) The slug of the region. + +## Import + +Import is supported using the following syntax: + +```shell +# Passwords can be imported using "org,database,branch,id" as the identifier. +# NOTE: 'id' is the 12-character password id (eg: k4fhp4kvj0mz) and not the password's name. +terraform import planetscale_branch.example "org,database,branch,id" +``` diff --git a/examples/data-sources/planetscale_branch_safe_migrations/data-source.tf b/examples/data-sources/planetscale_branch_safe_migrations/data-source.tf new file mode 100644 index 0000000..85cd90d --- /dev/null +++ b/examples/data-sources/planetscale_branch_safe_migrations/data-source.tf @@ -0,0 +1,9 @@ +data "planetscale_branch_safe_migrations" "example" { + organization = "example.com" + database = "example_db" + branch = "main" +} + +output "safe_migrations" { + value = data.planetscale_branch_safe_migrations.example +} diff --git a/examples/resources/planetscale_branch/import.sh b/examples/resources/planetscale_branch/import.sh new file mode 100644 index 0000000..917bd1e --- /dev/null +++ b/examples/resources/planetscale_branch/import.sh @@ -0,0 +1,2 @@ +# Branches can be imported using "org,database,branch" as the identifier. +terraform import planetscale_branch.example "org,database,branch" diff --git a/examples/resources/planetscale_branch_safe_migrations/import.sh b/examples/resources/planetscale_branch_safe_migrations/import.sh new file mode 100644 index 0000000..c85ef71 --- /dev/null +++ b/examples/resources/planetscale_branch_safe_migrations/import.sh @@ -0,0 +1,2 @@ +# Safe migrations configuration can be imported using "org,database,branch" as the identifier. +terraform import planetscale_branch_safe_migrations.example "org,database,branch" diff --git a/examples/resources/planetscale_branch_safe_migrations/resource.tf b/examples/resources/planetscale_branch_safe_migrations/resource.tf new file mode 100644 index 0000000..ac25363 --- /dev/null +++ b/examples/resources/planetscale_branch_safe_migrations/resource.tf @@ -0,0 +1,28 @@ +# Enable safe_migrations on default branch: +resource "planetscale_database" "example" { + organization = "example" + name = "example" + default_branch = "main" +} + +resource "planetscale_branch_safe_migrations" "main" { + organization = planetscale_branch.example.organization + database = planetscale_branch.example.database + branch = planetscale_branch.example.default_branch + enabled = true +} + +# Enable safe_migrations on a branch: +resource "planetscale_branch" "staging" { + organization = planetscale_branch.example.organization + database = planetscale_branch.example.database + parent_branch = planetscale_branch.example.default_branch + name = "staging" +} + +resource "planetscale_branch_safe_migrations" "staging" { + database = planetscale_database.example.name + organization = planetscale_database.example.organization + branch = planetscale_branch.staging.name + enabled = true +} diff --git a/examples/resources/planetscale_database/import.sh b/examples/resources/planetscale_database/import.sh new file mode 100644 index 0000000..ae4e668 --- /dev/null +++ b/examples/resources/planetscale_database/import.sh @@ -0,0 +1,2 @@ +# Databases can be imported using "org,database" as the identifier. +terraform import planetscale_branch.example "org,database" diff --git a/examples/resources/planetscale_password/import.sh b/examples/resources/planetscale_password/import.sh new file mode 100644 index 0000000..c5eeaed --- /dev/null +++ b/examples/resources/planetscale_password/import.sh @@ -0,0 +1,3 @@ +# Passwords can be imported using "org,database,branch,id" as the identifier. +# NOTE: 'id' is the 12-character password id (eg: k4fhp4kvj0mz) and not the password's name. +terraform import planetscale_branch.example "org,database,branch,id" diff --git a/internal/provider/branch_safe_migrations_data_source.go b/internal/provider/branch_safe_migrations_data_source.go new file mode 100644 index 0000000..bddfff3 --- /dev/null +++ b/internal/provider/branch_safe_migrations_data_source.go @@ -0,0 +1,76 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/planetscale/terraform-provider-planetscale/internal/client/planetscale" +) + +var ( + _ datasource.DataSource = &branchSafeMigrationsDataSource{} + _ datasource.DataSourceWithConfigure = &branchSafeMigrationsDataSource{} +) + +func newbranchSafeMigrationsDataSource() datasource.DataSource { + return &branchSafeMigrationsDataSource{} +} + +type branchSafeMigrationsDataSource struct { + client *planetscale.Client +} + +func (d *branchSafeMigrationsDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_branch_safe_migrations" +} + +func (d *branchSafeMigrationsDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Safe migration state on a PlanetScale branch.", + MarkdownDescription: "Safe migration state on a PlanetScale branch.", + Attributes: branchSafeMigrationsDataSourceSchemaAttribute, + } +} + +func (d *branchSafeMigrationsDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + client, ok := req.ProviderData.(*planetscale.Client) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *planetscale.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + d.client = client +} + +func (d *branchSafeMigrationsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data *branchSafeMigrationsDataSourceModel + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + res, err := d.client.GetBranch(ctx, data.Organization.ValueString(), data.Database.ValueString(), data.Branch.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Unable to read branch safe migrations status", err.Error()) + return + } + if res == nil { + resp.Diagnostics.AddError("Unable to read branch safe migrations status", "no data") + return + } + + state := branchSafeMigrationsFromClient(&res.Branch, data.Organization.ValueString(), data.Database.ValueString()) + if resp.Diagnostics.HasError() { + return + } + + diags := resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) +} diff --git a/internal/provider/branch_safe_migrations_data_source_test.go b/internal/provider/branch_safe_migrations_data_source_test.go new file mode 100644 index 0000000..115ee31 --- /dev/null +++ b/internal/provider/branch_safe_migrations_data_source_test.go @@ -0,0 +1,43 @@ +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccBranchSafeMigrationsDataSource(t *testing.T) { + dbName := acctest.RandomWithPrefix("testacc-safe-mig-ds") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccBranchSafeMigrationsDataSourceConfig(dbName), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.planetscale_branch_safe_migrations.test", "enabled", "false"), + ), + }, + }, + }) +} + +func testAccBranchSafeMigrationsDataSourceConfig(dbName string) string { + return fmt.Sprintf(` +resource "planetscale_database" "test" { + organization = "%s" + name = "%s" + cluster_size = "PS-10" + default_branch = "main" +} + +data "planetscale_branch_safe_migrations" "test" { + organization = "%s" + database = planetscale_database.test.name + branch = planetscale_database.test.default_branch +} +`, testAccOrg, dbName, testAccOrg) +} diff --git a/internal/provider/branch_safe_migrations_resource.go b/internal/provider/branch_safe_migrations_resource.go new file mode 100644 index 0000000..065ff8e --- /dev/null +++ b/internal/provider/branch_safe_migrations_resource.go @@ -0,0 +1,202 @@ +package provider + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/path" + "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/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/planetscale/terraform-provider-planetscale/internal/client/planetscale" +) + +var ( + _ resource.Resource = &branchSafeMigrationsResource{} + _ resource.ResourceWithImportState = &branchSafeMigrationsResource{} +) + +func newBranchSafeMigrationsResource() resource.Resource { + return &branchSafeMigrationsResource{} +} + +type branchSafeMigrationsResource struct { + client *planetscale.Client +} + +type branchSafeMigrationsResourceModel struct { + Organization types.String `tfsdk:"organization"` + Database types.String `tfsdk:"database"` + Branch types.String `tfsdk:"branch"` + Enabled types.Bool `tfsdk:"enabled"` + Id types.String `tfsdk:"id"` +} + +func (r *branchSafeMigrationsResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_branch_safe_migrations" // planetscale_branch_safe_migrations +} + +func (r *branchSafeMigrationsResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Manages safe migrations settings for a PlanetScale branch.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "The ID of the branch.", + Computed: true, + }, + "organization": schema.StringAttribute{ + Description: "The organization this branch belongs to.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "database": schema.StringAttribute{ + Description: "The database this branch belongs to.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "branch": schema.StringAttribute{ + Description: "The name of the branch to configure safe migrations on..", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "enabled": schema.BoolAttribute{ + Description: "Whether safe migrations are enabled for this branch.", + Required: true, + }, + }, + } +} + +func (r *branchSafeMigrationsResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + client, ok := req.ProviderData.(*planetscale.Client) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *planetscale.Client, got: %T.", req.ProviderData), + ) + return + } + r.client = client +} + +func (r *branchSafeMigrationsResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data *branchSafeMigrationsResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + var branch *planetscale.Branch + + if data.Enabled.ValueBool() { + res, err := r.client.EnableSafeMigrations(ctx, data.Organization.ValueString(), data.Database.ValueString(), data.Branch.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to enable safe migrations, got error: %s", err)) + return + } + branch = &res.Branch + } else { + res, err := r.client.DisableSafeMigrations(ctx, data.Organization.ValueString(), data.Database.ValueString(), data.Branch.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to disable safe migrations, got error: %s", err)) + return + } + branch = &res.Branch + } + + data.Id = types.StringValue(branch.Id) + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *branchSafeMigrationsResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data *branchSafeMigrationsResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + res, err := r.client.GetBranch(ctx, data.Organization.ValueString(), data.Database.ValueString(), data.Branch.ValueString()) + if err != nil { + if notFoundErr, ok := err.(*planetscale.GetBranchRes404); ok { + tflog.Warn(ctx, fmt.Sprintf("Branch not found, removing from state: %s", notFoundErr.Message)) + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read branch safe migrations, got error: %s", err)) + return + } + + data.Id = types.StringValue(res.Branch.Id) + data.Enabled = types.BoolValue(res.Branch.SafeMigrations) + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *branchSafeMigrationsResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data *branchSafeMigrationsResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + var branch *planetscale.Branch + + if data.Enabled.ValueBool() { + res, err := r.client.EnableSafeMigrations(ctx, data.Organization.ValueString(), data.Database.ValueString(), data.Branch.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to enable safe migrations, got error: %s", err)) + return + } + branch = &res.Branch + } else { + res, err := r.client.DisableSafeMigrations(ctx, data.Organization.ValueString(), data.Database.ValueString(), data.Branch.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to disable safe migrations, got error: %s", err)) + return + } + branch = &res.Branch + } + + data.Id = types.StringValue(branch.Id) + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *branchSafeMigrationsResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data *branchSafeMigrationsResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + _, err := r.client.DisableSafeMigrations(ctx, data.Organization.ValueString(), data.Database.ValueString(), data.Branch.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to disable safe migrations, got error: %s", err)) + return + } +} + +func (r *branchSafeMigrationsResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + idParts := strings.Split(req.ID, ",") + if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" { + resp.Diagnostics.AddError( + "Unexpected Import Identifier", + fmt.Sprintf("Expected import identifier with format: organization,database,branch. Got: %q", req.ID), + ) + return + } + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("organization"), idParts[0])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("database"), idParts[1])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("branch"), idParts[2])...) +} diff --git a/internal/provider/branch_safe_migrations_resource_test.go b/internal/provider/branch_safe_migrations_resource_test.go new file mode 100644 index 0000000..4407ca2 --- /dev/null +++ b/internal/provider/branch_safe_migrations_resource_test.go @@ -0,0 +1,92 @@ +package provider + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccBranchSafeMigrationsResource(t *testing.T) { + dbName := acctest.RandomWithPrefix("testacc-safe-mig-resource") + branchName := "main" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccBranchSafeMigrationsResourceConfig(dbName, branchName, true), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("planetscale_branch_safe_migrations.test", "enabled", "true"), + ), + }, + // ImportState testing + { + ResourceName: "planetscale_branch_safe_migrations.test", + ImportStateId: fmt.Sprintf("%s,%s,%s", testAccOrg, dbName, branchName), + ImportState: true, + ImportStateVerify: true, + }, + // Update and Read testing + { + Config: testAccBranchSafeMigrationsResourceConfig(dbName, branchName, false), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("planetscale_branch_safe_migrations.test", "enabled", "false"), + ), + }, + }, + }) +} + +// TestAccBranchSafeMigrationsResource_OutOfBandChange tests handling of out-of-band changes. +func TestAccBranchSafeMigrationsResource_OutOfBandChange(t *testing.T) { + dbName := acctest.RandomWithPrefix("testacc-safe-mig-resource-oob") + branchName := "main" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccBranchSafeMigrationsResourceConfig(dbName, branchName, true), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("planetscale_branch_safe_migrations.test", "enabled", "true"), + ), + }, + { + PreConfig: func() { + ctx := context.Background() + if _, err := testAccAPIClient.DisableSafeMigrations(ctx, testAccOrg, dbName, branchName); err != nil { + t.Fatalf("PreConfig: failed to disable safe migrations: %s", err) + } + }, + Config: testAccBranchSafeMigrationsResourceConfig(dbName, branchName, true), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("planetscale_branch_safe_migrations.test", "enabled", "true"), + ), + }, + }, + }) +} + +func testAccBranchSafeMigrationsResourceConfig(dbName string, branch string, enabled bool) string { + return fmt.Sprintf(` +resource "planetscale_database" "test" { + organization = "%s" + name = "%s" + cluster_size = "PS-10" + default_branch = "%s" +} + +resource "planetscale_branch_safe_migrations" "test" { + organization = planetscale_database.test.organization + database = planetscale_database.test.name + branch = planetscale_database.test.default_branch + enabled = %t +} +`, testAccOrg, dbName, branch, enabled) +} diff --git a/internal/provider/models_data_source.go b/internal/provider/models_data_source.go index b0ae661..082beb0 100644 --- a/internal/provider/models_data_source.go +++ b/internal/provider/models_data_source.go @@ -522,6 +522,10 @@ func branchDataSourceSchemaAttribute(computedName bool) map[string]schema.Attrib Description: "When a user last marked a backup restore checklist as completed.", Computed: true, }, + "safe_migrations": schema.BoolAttribute{ + Description: "Whether safe migrations are enabled for this branch.", + Computed: true, + }, "schema_last_updated_at": schema.StringAttribute{ Description: "When the schema for the branch was last updated.", Computed: true, @@ -576,6 +580,7 @@ type branchDataSourceModel struct { Region *regionDataSourceModel `tfsdk:"region"` RestoreChecklistCompletedAt types.String `tfsdk:"restore_checklist_completed_at"` RestoredFromBranch *restoredFromBranchDataSourceModel `tfsdk:"restored_from_branch"` + SafeMigrations types.Bool `tfsdk:"safe_migrations"` SchemaLastUpdatedAt types.String `tfsdk:"schema_last_updated_at"` ShardCount types.Float64 `tfsdk:"shard_count"` Sharded types.Bool `tfsdk:"sharded"` @@ -603,6 +608,7 @@ func branchFromClient(branch *planetscale.Branch, organization, database string, Production: types.BoolValue(branch.Production), Ready: types.BoolValue(branch.Ready), RestoreChecklistCompletedAt: types.StringPointerValue(branch.RestoreChecklistCompletedAt), + SafeMigrations: types.BoolValue(branch.SafeMigrations), SchemaLastUpdatedAt: types.StringValue(branch.SchemaLastUpdatedAt), ShardCount: types.Float64PointerValue(branch.ShardCount), Sharded: types.BoolValue(branch.Sharded), @@ -610,6 +616,44 @@ func branchFromClient(branch *planetscale.Branch, organization, database string, } } +var branchSafeMigrationsDataSourceSchemaAttribute = map[string]schema.Attribute{ + "organization": schema.StringAttribute{ + Description: "The organization this branch belongs to.", + Required: true, + }, + "database": schema.StringAttribute{ + Description: "The database this branch belongs to.", + Required: true, + }, + "branch": schema.StringAttribute{ + Description: "The name of the branch this safe migrations configuration belongs to.", + Required: true, + }, + "enabled": schema.BoolAttribute{ + Description: "Whether safe migrations are enabled for this branch.", + Computed: true, + }, +} + +type branchSafeMigrationsDataSourceModel struct { + Organization types.String `tfsdk:"organization"` + Database types.String `tfsdk:"database"` + Branch types.String `tfsdk:"branch"` + Enabled types.Bool `tfsdk:"enabled"` +} + +func branchSafeMigrationsFromClient(branch *planetscale.Branch, organization, database string) *branchSafeMigrationsDataSourceModel { + if branch == nil { + return nil + } + return &branchSafeMigrationsDataSourceModel{ + Organization: types.StringValue(organization), + Database: types.StringValue(database), + Branch: types.StringValue(branch.Name), + Enabled: types.BoolValue(branch.SafeMigrations), + } +} + var actorDataSourceSchemaAttribute = map[string]schema.Attribute{ "avatar_url": schema.StringAttribute{ Computed: true, Description: "The URL of the actor's avatar", diff --git a/internal/provider/organization_data_source_test.go b/internal/provider/organization_data_source_test.go index 55fa977..fdc23de 100644 --- a/internal/provider/organization_data_source_test.go +++ b/internal/provider/organization_data_source_test.go @@ -1,6 +1,7 @@ package provider import ( + "fmt" "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -13,9 +14,9 @@ func TestAccOrganizationDataSource(t *testing.T) { Steps: []resource.TestStep{ // Read testing { - Config: testAccOrganizationDataSourceConfig, + Config: testAccOrganizationDataSourceConfig(), Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("data.planetscale_organization.test", "name", "planetscale-terraform-testing"), + resource.TestCheckResourceAttr("data.planetscale_organization.test", "name", testAccOrg), resource.TestCheckResourceAttrSet("data.planetscale_organization.test", "billing_email"), resource.TestCheckResourceAttrSet("data.planetscale_organization.test", "created_at"), resource.TestCheckResourceAttrSet("data.planetscale_organization.test", "database_count"), @@ -36,8 +37,10 @@ func TestAccOrganizationDataSource(t *testing.T) { }) } -const testAccOrganizationDataSourceConfig = ` +func testAccOrganizationDataSourceConfig() string { + return fmt.Sprintf(` data "planetscale_organization" "test" { - name = "planetscale-terraform-testing" + name = "%s" +} +`, testAccOrg) } -` diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 0c5f083..205d571 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -175,6 +175,7 @@ func (p *PlanetScaleProvider) Resources(ctx context.Context) []func() resource.R return []func() resource.Resource{ newDatabaseResource, newBranchResource, + newBranchSafeMigrationsResource, newBackupResource, newPasswordResource, } @@ -193,6 +194,7 @@ func (p *PlanetScaleProvider) DataSources(ctx context.Context) []func() datasour newBranchDataSource, newBranchSchemaDataSource, newBranchSchemaLintDataSource, + newbranchSafeMigrationsDataSource, newBackupDataSource, newBackupsDataSource, newPasswordDataSource,