diff --git a/.env.example b/.env.example
index 95ae07ec..97597b9a 100644
--- a/.env.example
+++ b/.env.example
@@ -17,3 +17,8 @@ BA_TF_ACC_VAR_region_region_id=
# data_source_pgd
BA_TF_ACC_VAR_pgd_project_id=
BA_TF_ACC_VAR_pgd_name=
+
+# data_source_region
+BA_TF_ACC_VAR_ds_region_project_id=
+BA_TF_ACC_VAR_ds_region_provider=
+BA_TF_ACC_VAR_ds_region_region_id=
diff --git a/docs/data-sources/region.md b/docs/data-sources/region.md
index fa2537e5..d02de644 100644
--- a/docs/data-sources/region.md
+++ b/docs/data-sources/region.md
@@ -1,5 +1,5 @@
# biganimal_region (Data Source)
-The region data source shows the available regions within a cloud provider.
+
## Example Usage
```terraform
@@ -8,8 +8,8 @@ variable "cloud_provider" {
description = "Cloud Provider"
validation {
- condition = contains(["aws", "azure"], var.cloud_provider)
- error_message = "Please select one of the supported regions: aws, azure."
+ condition = contains(["aws", "azure", "bah:aws"], var.cloud_provider)
+ error_message = "Please select one of the supported regions: aws, azure or bah:aws."
}
}
@@ -40,7 +40,7 @@ output "cloud_provider_id" {
### Required
-- `cloud_provider` (String) Cloud provider to list the regions. For example, "aws" or "azure".
+- `cloud_provider` (String) Cloud provider to list the regions. For example, "aws", "azure" or "bah:aws".
- `project_id` (String) BigAnimal Project ID.
### Optional
@@ -50,15 +50,15 @@ output "cloud_provider_id" {
### Read-Only
-- `id` (String) The ID of this resource.
-- `regions` (List of Object) Region information. (see [below for nested schema](#nestedatt--regions))
+- `id` (String) Datasource ID.
+- `regions` (Attributes List) Region information. (see [below for nested schema](#nestedatt--regions))
### Nested Schema for `regions`
Read-Only:
-- `continent` (String)
-- `name` (String)
-- `region_id` (String)
-- `status` (String)
+- `continent` (String) Continent that region belongs to.
+- `name` (String) Region name of the region.
+- `region_id` (String) Region ID of the region.
+- `status` (String) Region status of the region.
diff --git a/docs/resources/region.md b/docs/resources/region.md
index 26b333d2..af081226 100644
--- a/docs/resources/region.md
+++ b/docs/resources/region.md
@@ -43,7 +43,7 @@ output "region_continent" {
### Required
-- `cloud_provider` (String) Cloud provider. For example, "aws" or "azure".
+- `cloud_provider` (String) Cloud provider. For example, "aws", "azure" or "bah:aws".
- `project_id` (String) BigAnimal Project ID.
- `region_id` (String) Region ID of the region. For example, "germanywestcentral" in the Azure cloud provider or "eu-west-1" in the AWS cloud provider.
@@ -55,7 +55,7 @@ output "region_continent" {
### Read-Only
- `continent` (String) Continent that region belongs to. For example, "Asia", "Australia", or "Europe".
-- `id` (String) The ID of this resource.
+- `id` (String) Resource ID of the region.
- `name` (String) Region name of the region. For example, "Germany West Central" or "EU West 1".
@@ -66,3 +66,12 @@ Optional:
- `create` (String)
- `delete` (String)
- `update` (String)
+
+## Import
+
+Import is supported using the following syntax:
+
+```shell
+# terraform import biganimal_project. //
+terraform import biganimal_region.this prj_deadbeef01234567/aws/eu-west-1
+```
diff --git a/examples/data-sources/biganimal_region/data-source.tf b/examples/data-sources/biganimal_region/data-source.tf
index c8a724f4..61236156 100644
--- a/examples/data-sources/biganimal_region/data-source.tf
+++ b/examples/data-sources/biganimal_region/data-source.tf
@@ -3,8 +3,8 @@ variable "cloud_provider" {
description = "Cloud Provider"
validation {
- condition = contains(["aws", "azure"], var.cloud_provider)
- error_message = "Please select one of the supported regions: aws, azure."
+ condition = contains(["aws", "azure", "bah:aws"], var.cloud_provider)
+ error_message = "Please select one of the supported regions: aws, azure or bah:aws."
}
}
diff --git a/examples/resources/biganimal_region/import.sh b/examples/resources/biganimal_region/import.sh
new file mode 100644
index 00000000..ae6a01a2
--- /dev/null
+++ b/examples/resources/biganimal_region/import.sh
@@ -0,0 +1,2 @@
+# terraform import biganimal_project. //
+terraform import biganimal_region.this prj_deadbeef01234567/aws/eu-west-1
diff --git a/go.mod b/go.mod
index 52adfbf8..76fcf8fa 100644
--- a/go.mod
+++ b/go.mod
@@ -9,6 +9,8 @@ require (
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320
github.com/hashicorp/terraform-plugin-docs v0.15.0
github.com/hashicorp/terraform-plugin-framework v1.3.1
+ github.com/hashicorp/terraform-plugin-framework-timeouts v0.3.1
+ github.com/hashicorp/terraform-plugin-framework-validators v0.10.0
github.com/hashicorp/terraform-plugin-go v0.16.0
github.com/hashicorp/terraform-plugin-log v0.9.0
github.com/hashicorp/terraform-plugin-mux v0.10.0
diff --git a/go.sum b/go.sum
index efbef7b6..b1c5e8f5 100644
--- a/go.sum
+++ b/go.sum
@@ -95,6 +95,10 @@ github.com/hashicorp/terraform-plugin-docs v0.15.0 h1:W5xYB5kCUBqO7lyjE2UMmUBh95
github.com/hashicorp/terraform-plugin-docs v0.15.0/go.mod h1:K5Taof1Y7sL4dw6Ie0qMFyQnHN0W+RSVMD0iIyFDFJc=
github.com/hashicorp/terraform-plugin-framework v1.3.1 h1:uhd+SuyuDq3oh5VB2Toq5IPyaC5XFAUf9vUFKBmNNOk=
github.com/hashicorp/terraform-plugin-framework v1.3.1/go.mod h1:A1WD3Ry7FhrThViUTbkx4ZDsMq9oaAv4U9oTI8bBzCU=
+github.com/hashicorp/terraform-plugin-framework-timeouts v0.3.1 h1:5GhozvHUsrqxqku+yd0UIRTkmDLp2QPX5paL1Kq5uUA=
+github.com/hashicorp/terraform-plugin-framework-timeouts v0.3.1/go.mod h1:ThtYDU8p6sJ9+SI+TYxXrw28vXxgBwYOpoPv1EojSJI=
+github.com/hashicorp/terraform-plugin-framework-validators v0.10.0 h1:4L0tmy/8esP6OcvocVymw52lY0HyQ5OxB7VNl7k4bS0=
+github.com/hashicorp/terraform-plugin-framework-validators v0.10.0/go.mod h1:qdQJCdimB9JeX2YwOpItEu+IrfoJjWQ5PhLpAOMDQAE=
github.com/hashicorp/terraform-plugin-go v0.16.0 h1:DSOQ0rz5FUiVO4NUzMs8ln9gsPgHMTsfns7Nk+6gPuE=
github.com/hashicorp/terraform-plugin-go v0.16.0/go.mod h1:4sn8bFuDbt+2+Yztt35IbOrvZc0zyEi87gJzsTgCES8=
github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0=
diff --git a/main.go b/main.go
index d486ad59..54df32a7 100644
--- a/main.go
+++ b/main.go
@@ -3,6 +3,8 @@ package main
import (
"context"
"flag"
+ "log"
+
"github.com/EnterpriseDB/terraform-provider-biganimal/pkg/provider"
"github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
@@ -11,7 +13,6 @@ import (
"github.com/hashicorp/terraform-plugin-mux/tf5to6server"
"github.com/hashicorp/terraform-plugin-mux/tf6muxserver"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
- "log"
)
// Run "go generate" to format example terraform files and generate the docs for the registry/website
diff --git a/pkg/models/region.go b/pkg/models/region.go
index 4ee7d740..14ce0c81 100644
--- a/pkg/models/region.go
+++ b/pkg/models/region.go
@@ -1,10 +1,10 @@
package models
type Region struct {
- Id string `json:"regionId,omitempty" mapstructure:"region_id"`
- Name string `json:"regionName,omitempty" mapstructure:"name,omitempty"`
- Status string `json:"status,omitempty" mapstructure:"status,omitempty"`
- Continent string `json:"continent,omitempty" mapstructure:"continent,omitempty"`
+ Id string `json:"regionId,omitempty" tfsdk:"region_id"`
+ Name string `json:"regionName,omitempty" tfsdk:"name"`
+ Status string `json:"status,omitempty" tfsdk:"status"`
+ Continent string `json:"continent,omitempty" tfsdk:"continent"`
}
func (r Region) String() string {
diff --git a/pkg/provider/data_source_region.go b/pkg/provider/data_source_region.go
index 397710a7..8a58a55e 100644
--- a/pkg/provider/data_source_region.go
+++ b/pkg/provider/data_source_region.go
@@ -2,114 +2,141 @@ package provider
import (
"context"
- "errors"
"fmt"
+ "strconv"
+ "time"
"github.com/EnterpriseDB/terraform-provider-biganimal/pkg/api"
- "github.com/EnterpriseDB/terraform-provider-biganimal/pkg/utils"
- "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+ "github.com/EnterpriseDB/terraform-provider-biganimal/pkg/models"
+ "github.com/hashicorp/terraform-plugin-framework/datasource"
+ "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+ "github.com/hashicorp/terraform-plugin-framework/types"
)
-// RegionResource is a struct to namespace all the functions
-// involved in the Region Resource. When multiple resources and objects
-// are in the same pkg/provider, then it's difficult to namespace things well
-type RegionData struct{}
+var _ datasource.DataSourceWithConfigure = ®ionsDataSource{}
-func NewRegionData() *RegionData {
- return &RegionData{}
+// NewRegionsDataSource is a helper function to simplify the provider implementation.
+func NewRegionsDataSource() datasource.DataSource {
+ return ®ionsDataSource{}
}
-func (r *RegionData) Schema() *schema.Resource {
- return &schema.Resource{
- Description: "The region data source shows the available regions within a cloud provider.",
- ReadContext: r.Read,
+// regionsDataSource is the data source implementation.
+type regionsDataSource struct {
+ client *api.RegionClient
+}
+
+// Configure adds the provider configured client to the data source.
+func (r *regionsDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+ if req.ProviderData == nil {
+ return
+ }
+
+ r.client = req.ProviderData.(*api.API).RegionClient()
+}
+
+func (r *regionsDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_region"
+}
- //{
- // "regionId": "eu-west-1",
- // "regionName": "EU West 1",
- // "status": "ACTIVE",
- // "continent": "Europe"
- //}
- Schema: map[string]*schema.Schema{
- "regions": {
+func (r *regionsDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Description: "Datasource ID.",
+ Computed: true,
+ },
+ "regions": schema.ListNestedAttribute{
Description: "Region information.",
- Type: schema.TypeList,
Computed: true,
- Elem: &schema.Resource{
- Schema: map[string]*schema.Schema{
- "region_id": {
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "region_id": schema.StringAttribute{
Description: "Region ID of the region.",
- Type: schema.TypeString,
Computed: true,
},
- "name": {
+ "name": schema.StringAttribute{
Description: "Region name of the region.",
- Type: schema.TypeString,
Computed: true,
},
- "status": {
+ "status": schema.StringAttribute{
Description: "Region status of the region.",
- Type: schema.TypeString,
Computed: true,
},
- "continent": {
+ "continent": schema.StringAttribute{
Description: "Continent that region belongs to.",
- Type: schema.TypeString,
Computed: true,
},
},
},
},
- "cloud_provider": {
- Description: "Cloud provider to list the regions. For example, \"aws\" or \"azure\".",
- Type: schema.TypeString,
+
+ "cloud_provider": schema.StringAttribute{
+ Description: "Cloud provider to list the regions. For example, \"aws\", \"azure\" or \"bah:aws\".",
Required: true,
},
- "project_id": {
- Description: "BigAnimal Project ID.",
- Type: schema.TypeString,
- Required: true,
- ValidateDiagFunc: validateProjectId,
+ "project_id": schema.StringAttribute{
+ Description: "BigAnimal Project ID.",
+ Required: true,
+ Validators: []validator.String{
+ ProjectIdValidator(),
+ },
},
- "query": {
+ "query": schema.StringAttribute{
Description: "Query to filter region list.",
- Type: schema.TypeString,
Optional: true,
},
- "region_id": {
+ "region_id": schema.StringAttribute{
Description: "Unique region ID. For example, \"germanywestcentral\" in the Azure cloud provider, \"eu-west-1\" in the AWS cloud provider.",
- Type: schema.TypeString,
Optional: true,
},
},
}
}
-func (r *RegionData) Read(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
- diags := diag.Diagnostics{}
- client := api.BuildAPI(meta).RegionClient()
- cloud_provider := d.Get("cloud_provider").(string)
- projectId := d.Get("project_id").(string)
-
- query := d.Get("query").(string)
+type regionsDataSourceModel struct {
+ ID *string `tfsdk:"id"`
+ ProjectId *string `tfsdk:"project_id"`
+ CloudProvider *string `tfsdk:"cloud_provider"`
+ RegionId *string `tfsdk:"region_id"`
+ Query types.String `tfsdk:"query"`
+ Regions []*models.Region `tfsdk:"regions"`
+}
- id, ok := d.Get("region_id").(string)
- if ok {
- query = id
+func (r *regionsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+ var cfg regionsDataSourceModel
+ diags := req.Config.Get(ctx, &cfg)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
}
- regions, err := client.List(ctx, projectId, cloud_provider, query)
- if err != nil {
- return fromBigAnimalErr(err)
- }
+ regions := []*models.Region{}
+ if cfg.RegionId != nil {
+ region, err := r.client.Read(ctx, *cfg.ProjectId, *cfg.CloudProvider, *cfg.RegionId)
+ if err != nil {
+ if appendDiagFromBAErr(err, &diags) {
+ return
+ }
+ diags.AddError(fmt.Sprintf("Error reading region by id: %q", *cfg.RegionId), err.Error())
+ return
+ }
+ regions = append(regions, region)
- if id != "" && len(regions) != 1 {
- return diag.FromErr(errors.New("unable to find a unique region"))
+ } else {
+ respRegions, err := r.client.List(ctx, *cfg.ProjectId, *cfg.CloudProvider, cfg.Query.ValueString())
+ if err != nil {
+ if appendDiagFromBAErr(err, &diags) {
+ return
+ }
+ diags.AddError(fmt.Sprintf("Error reading region by query: %q", cfg.Query.ValueString()), err.Error())
+ return
+ }
+ regions = respRegions
}
- utils.SetOrPanic(d, "regions", regions)
- d.SetId(fmt.Sprintf("%s/%s", cloud_provider, query))
-
- return diags
+ cfg.Regions = append(cfg.Regions, regions...)
+ resourceID := strconv.FormatInt(time.Now().Unix(), 10)
+ cfg.ID = &resourceID
+ resp.Diagnostics.Append(resp.State.Set(ctx, &cfg)...)
}
diff --git a/pkg/provider/data_source_region_test.go b/pkg/provider/data_source_region_test.go
new file mode 100644
index 00000000..e41c97ca
--- /dev/null
+++ b/pkg/provider/data_source_region_test.go
@@ -0,0 +1,59 @@
+package provider
+
+import (
+ "fmt"
+ "os"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+)
+
+func TestAccDataSourceRegion_basic(t *testing.T) {
+ var (
+ acc_env_vars_checklist = []string{
+ "BA_TF_ACC_VAR_ds_region_project_id",
+ "BA_TF_ACC_VAR_ds_region_provider",
+ "BA_TF_ACC_VAR_ds_region_region_id",
+ }
+ projectId = os.Getenv("BA_TF_ACC_VAR_ds_region_project_id")
+ provider = os.Getenv("BA_TF_ACC_VAR_ds_region_provider")
+ regionId = os.Getenv("BA_TF_ACC_VAR_ds_region_region_id")
+ )
+
+ resource.ParallelTest(t, resource.TestCase{
+ PreCheck: func() {
+ testAccPreCheck(t)
+ testAccResourcePreCheck(t, "datasource regions", acc_env_vars_checklist)
+ },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: regionsDataSourceConfig(projectId, provider, regionId),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttrPair("data.biganimal_region.test", "regions.0.name", "biganimal_region.test", "name"),
+ resource.TestCheckResourceAttrPair("data.biganimal_region.test", "regions.0.status", "biganimal_region.test", "status"),
+ ),
+ },
+ },
+ })
+}
+
+func regionsDataSourceConfig(projectId, provider, regionId string) string {
+ return fmt.Sprintf(`data "biganimal_region" "test" {
+ project_id = "%[1]s"
+ cloud_provider = "%[2]s"
+ region_id = "%[3]s"
+}
+
+resource "biganimal_region" "test" {
+ project_id = "%[1]s"
+ cloud_provider = "%[2]s"
+ region_id = "%[3]s"
+ # no need to active region actually for saving time'
+ status = "INACTIVE"
+}
+
+
+
+`, projectId, provider, regionId)
+}
diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go
index 374d0dc5..176c7766 100644
--- a/pkg/provider/provider.go
+++ b/pkg/provider/provider.go
@@ -17,13 +17,11 @@ import (
const DefaultAPIURL = "https://portal.biganimal.com/api/v3"
var (
- resourceRegion = NewRegionResource()
resourceCluster = NewClusterResource()
resourceAWSConnection = NewAWSConnectionResource()
resourceAzureConnection = NewAzureConnectionResource()
resourceFAReplica = NewFAReplicaResource()
- dataRegion = NewRegionData()
dataCluster = NewClusterData()
dataAWSConnection = NewAWSConnectionData()
dataFaReplica = NewFAReplicaData()
@@ -53,14 +51,12 @@ func NewSDKProvider(version string) func() *sdkschema.Provider {
},
DataSourcesMap: map[string]*sdkschema.Resource{
"biganimal_cluster": dataCluster.Schema(),
- "biganimal_region": dataRegion.Schema(),
"biganimal_faraway_replica": dataFaReplica.Schema(),
"biganimal_aws_connection": dataAWSConnection.Schema(),
},
ResourcesMap: map[string]*sdkschema.Resource{
"biganimal_cluster": resourceCluster.Schema(),
- "biganimal_region": resourceRegion.Schema(),
"biganimal_aws_connection": resourceAWSConnection.Schema(),
"biganimal_azure_connection": resourceAzureConnection.Schema(),
"biganimal_faraway_replica": resourceFAReplica.Schema(),
@@ -181,11 +177,13 @@ func (b bigAnimalProvider) DataSources(ctx context.Context) []func() datasource.
return []func() datasource.DataSource{
NewProjectsDataSource,
NewPgdDataSource,
+ NewRegionsDataSource,
}
}
func (b bigAnimalProvider) Resources(ctx context.Context) []func() resource.Resource {
return []func() resource.Resource{
NewProjectResource,
+ NewRegionResource,
}
}
diff --git a/pkg/provider/resource_project.go b/pkg/provider/resource_project.go
index 7f869968..db9ef614 100644
--- a/pkg/provider/resource_project.go
+++ b/pkg/provider/resource_project.go
@@ -2,6 +2,7 @@ package provider
import (
"context"
+
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/EnterpriseDB/terraform-provider-biganimal/pkg/api"
diff --git a/pkg/provider/resource_region.go b/pkg/provider/resource_region.go
index 722ba053..8bed744e 100644
--- a/pkg/provider/resource_region.go
+++ b/pkg/provider/resource_region.go
@@ -4,185 +4,244 @@ import (
"context"
"errors"
"fmt"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/tfsdk"
+ "strings"
"time"
"github.com/EnterpriseDB/terraform-provider-biganimal/pkg/api"
- "github.com/EnterpriseDB/terraform-provider-biganimal/pkg/utils"
- "github.com/hashicorp/terraform-plugin-log/tflog"
- "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+ "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
+ frameworkdiag "github.com/hashicorp/terraform-plugin-framework/diag"
+ "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/stringdefault"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
-// RegionResource is a struct to namespace all the functions
-// involved in the Region Resource. When multiple resources and objects
-// are in the same pkg/provider, then it's difficult to namespace things well
-type RegionResource struct{}
-
-func NewRegionResource() *RegionResource {
- return &RegionResource{}
+type regionResource struct {
+ client *api.RegionClient
}
-func (r *RegionResource) Schema() *schema.Resource {
- return &schema.Resource{
- Description: "The region resource is used to manage regions for a given cloud provider. See [Activating regions](https://www.enterprisedb.com/docs/biganimal/latest/getting_started/activating_regions/) for more details.",
-
- CreateContext: r.Create,
- ReadContext: r.Read,
- UpdateContext: r.Update,
- DeleteContext: r.Delete,
+func (r regionResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_region"
+}
- Timeouts: &schema.ResourceTimeout{
- Create: schema.DefaultTimeout(60 * time.Minute),
- Update: schema.DefaultTimeout(60 * time.Minute),
- Delete: schema.DefaultTimeout(60 * time.Minute),
+func (r regionResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ MarkdownDescription: "The region resource is used to manage regions for a given cloud provider. See [Activating regions](https://www.enterprisedb.com/docs/biganimal/latest/getting_started/activating_regions/) for more details.",
+ Blocks: map[string]schema.Block{
+ "timeouts": timeouts.Block(ctx,
+ timeouts.Opts{Create: true, Delete: true, Update: true}),
},
- Schema: map[string]*schema.Schema{
- "cloud_provider": {
- Description: "Cloud provider. For example, \"aws\" or \"azure\".",
- Type: schema.TypeString,
- Required: true,
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ MarkdownDescription: "Resource ID of the region.",
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
},
- "project_id": {
- Description: "BigAnimal Project ID.",
- Type: schema.TypeString,
- Required: true,
- ValidateDiagFunc: validateProjectId,
+ "cloud_provider": schema.StringAttribute{
+ MarkdownDescription: "Cloud provider. For example, \"aws\", \"azure\" or \"bah:aws\".",
+ Required: true,
},
- "region_id": {
- Description: "Region ID of the region. For example, \"germanywestcentral\" in the Azure cloud provider or \"eu-west-1\" in the AWS cloud provider.",
- Type: schema.TypeString,
- Required: true,
+ "project_id": schema.StringAttribute{
+ MarkdownDescription: "BigAnimal Project ID.",
+ Required: true,
+ Validators: []validator.String{
+ ProjectIdValidator(),
+ },
},
- "name": {
- Description: "Region name of the region. For example, \"Germany West Central\" or \"EU West 1\".",
- Type: schema.TypeString,
- Computed: true,
+ "region_id": schema.StringAttribute{
+ MarkdownDescription: "Region ID of the region. For example, \"germanywestcentral\" in the Azure cloud provider or \"eu-west-1\" in the AWS cloud provider.",
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
},
- "status": {
- Description: "Region status of the region. For example, \"ACTIVE\", \"INACTIVE\", or \"SUSPENDED\".",
- Type: schema.TypeString,
- Optional: true,
- Default: api.REGION_ACTIVE,
+ "name": schema.StringAttribute{
+ MarkdownDescription: "Region name of the region. For example, \"Germany West Central\" or \"EU West 1\".",
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
},
- "continent": {
- Description: "Continent that region belongs to. For example, \"Asia\", \"Australia\", or \"Europe\".",
- Type: schema.TypeString,
- Computed: true,
+ "status": schema.StringAttribute{
+ MarkdownDescription: "Region status of the region. For example, \"ACTIVE\", \"INACTIVE\", or \"SUSPENDED\".",
+ Optional: true,
+ Computed: true,
+ Default: stringdefault.StaticString(api.REGION_ACTIVE),
+ },
+ "continent": schema.StringAttribute{
+ MarkdownDescription: "Continent that region belongs to. For example, \"Asia\", \"Australia\", or \"Europe\".",
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
},
},
}
}
-func (r *RegionResource) Create(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
- return r.Update(ctx, d, meta)
-}
-
-func (r *RegionResource) Read(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
- if err := r.read(ctx, d, meta); err != nil {
- return fromBigAnimalErr(err)
+func (r *regionResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) {
+ if req.ProviderData == nil {
+ return
}
- return diag.Diagnostics{}
+
+ r.client = req.ProviderData.(*api.API).RegionClient()
}
-func (r *RegionResource) read(ctx context.Context, d *schema.ResourceData, meta any) error {
- client := api.BuildAPI(meta).RegionClient()
- projectId := d.Get("project_id").(string)
- cloud_provider := d.Get("cloud_provider").(string)
+type Region struct {
+ ProjectID *string `tfsdk:"project_id"`
+ CloudProvider *string `tfsdk:"cloud_provider"`
+ RegionID *string `tfsdk:"region_id"`
+ ID *string `tfsdk:"id"`
+ Name *string `tfsdk:"name"`
+ Continent *string `tfsdk:"continent"`
+ Status *string `tfsdk:"status"`
- id := d.Get("region_id").(string)
- region, err := client.Read(ctx, projectId, cloud_provider, id)
- if err != nil {
- return err
+ Timeouts timeouts.Value `tfsdk:"timeouts"`
+}
+
+func (r regionResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ var config Region
+ diags := req.Config.Get(ctx, &config)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
}
- utils.SetOrPanic(d, "name", region.Name)
- utils.SetOrPanic(d, "status", region.Status)
- utils.SetOrPanic(d, "continent", region.Continent)
- d.SetId(fmt.Sprintf("%s/%s", cloud_provider, id))
+ resp.Diagnostics.Append(r.ensureStatueUpdated(ctx, config)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
- return nil
+ resp.Diagnostics.Append(r.writeState(ctx, config, &resp.State)...)
}
-func (r *RegionResource) Update(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
- client := api.BuildAPI(meta).RegionClient()
-
- cloudProvider := d.Get("cloud_provider").(string)
- id := d.Get("region_id").(string)
- projectId := d.Get("project_id").(string)
- desiredState := d.Get("status").(string)
-
- region, err := client.Read(ctx, projectId, cloudProvider, id)
- if err != nil {
- return fromBigAnimalErr(err)
+func (r *regionResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+ var state Region
+ diags := req.State.Get(ctx, &state)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
}
- utils.SetOrPanic(d, "name", region.Name)
- utils.SetOrPanic(d, "continent", region.Continent)
- utils.SetOrPanic(d, "status", desiredState)
- d.SetId(fmt.Sprintf("%s/%s", cloudProvider, id))
+ resp.Diagnostics.Append(r.writeState(ctx, state, &resp.State)...)
+}
- if desiredState == region.Status { // no change, exit early
- return diag.Diagnostics{}
+func (r *regionResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ var plan Region
+ diags := req.Plan.Get(ctx, &plan)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
}
- tflog.Debug(ctx, fmt.Sprintf("updating region from %s to %s", region.Status, desiredState))
- if err = client.Update(ctx, desiredState, projectId, cloudProvider, id); err != nil {
- return fromBigAnimalErr(err)
+ resp.Diagnostics.Append(r.ensureStatueUpdated(ctx, plan)...)
+ if resp.Diagnostics.HasError() {
+ return
}
- // retry until we get success
- err = retry.RetryContext(
- ctx,
- d.Timeout(schema.TimeoutCreate)-time.Minute,
- r.retryFunc(ctx, d, meta, cloudProvider, id, desiredState))
- if err != nil {
- return diag.FromErr(err)
+ resp.Diagnostics.Append(r.writeState(ctx, plan, &resp.State)...)
+}
+
+func (r *regionResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+ var state Region
+ diags := req.State.Get(ctx, &state)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
}
- return diag.Diagnostics{}
+
+ *state.Status = api.REGION_INACTIVE
+ resp.Diagnostics.Append(r.ensureStatueUpdated(ctx, state)...)
}
-func (r *RegionResource) Delete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
- client := api.BuildAPI(meta).RegionClient()
+func (r *regionResource) ensureStatueUpdated(ctx context.Context, region Region) frameworkdiag.Diagnostics {
+ if region.Status == nil {
+ status := api.REGION_ACTIVE
+ region.Status = &status
+ }
- projectId := d.Get("project_id").(string)
- cloudProvider := d.Get("cloud_provider").(string)
- id := d.Get("region_id").(string)
- desiredState := api.REGION_INACTIVE
- if err := client.Update(ctx, api.REGION_INACTIVE, projectId, cloudProvider, id); err != nil {
- return fromBigAnimalErr(err)
+ diags := frameworkdiag.Diagnostics{}
+ if err := r.client.Update(ctx, *region.Status, *region.ProjectID, *region.CloudProvider, *region.RegionID); err != nil {
+ if appendDiagFromBAErr(err, &diags) {
+ return diags
+ }
+ diags.AddError(fmt.Sprintf("Error turning region %q into %q status", *region.RegionID, *region.Status), err.Error())
+ return diags
+ }
+
+ timeout, diagnostics := region.Timeouts.Create(ctx, 60*time.Minute)
+ if diagnostics != nil {
+ return diagnostics
}
- // retry until we get success
err := retry.RetryContext(
ctx,
- d.Timeout(schema.TimeoutDelete)-time.Minute,
- r.retryFunc(ctx, d, meta, cloudProvider, id, desiredState))
+ timeout-time.Minute,
+ r.retryFunc(ctx, region))
if err != nil {
- return diag.FromErr(err)
+ if appendDiagFromBAErr(err, &diags) {
+ return diags
+ }
+ diags.AddError(fmt.Sprintf("Error reading region %s", *region.RegionID), err.Error())
}
+ return diags
+}
- return diag.Diagnostics{}
+func (r *regionResource) writeState(ctx context.Context, region Region, state *tfsdk.State) frameworkdiag.Diagnostics {
+ read, err := r.client.Read(ctx, *region.ProjectID, *region.CloudProvider, *region.RegionID)
+ if err != nil {
+ diags := frameworkdiag.Diagnostics{}
+ if appendDiagFromBAErr(err, &diags) {
+ return diags
+ }
+ diags.AddError(fmt.Sprintf("Error reading region %s", *region.RegionID), err.Error())
+ return diags
+ }
+ id := fmt.Sprintf("%s/%s/%s", *region.ProjectID, *region.CloudProvider, *region.RegionID)
+ region.ID = &id
+ region.Name = &read.Name
+ region.Status = &read.Status
+ region.Continent = &read.Continent
+ return state.Set(ctx, ®ion)
}
-func (r *RegionResource) retryFunc(ctx context.Context, d *schema.ResourceData, meta any, cloudProvider, regionId, desiredState string) retry.RetryFunc {
- client := api.BuildAPI(meta).RegionClient()
+func (r *regionResource) retryFunc(ctx context.Context, region Region) retry.RetryFunc {
return func() *retry.RetryError {
- projectId := d.Get("project_id").(string)
- region, err := client.Read(ctx, projectId, cloudProvider, regionId)
+ curr, err := r.client.Read(ctx, *region.ProjectID, *region.CloudProvider, *region.RegionID)
if err != nil {
- return retry.NonRetryableError(fmt.Errorf("error describing instance: %s", err))
+ return retry.NonRetryableError(err)
}
- if region.Status != desiredState {
+ if curr.Status != *region.Status {
return retry.RetryableError(errors.New("operation incomplete"))
}
-
- if err := r.read(ctx, d, meta); err != nil {
- return retry.NonRetryableError(err)
- }
-
return nil
}
}
+
+func (r regionResource) 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: project_id/cloud_provider/region_id. Got: %q", req.ID),
+ )
+ return
+ }
+
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...)
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("cloud_provider"), idParts[1])...)
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region_id"), idParts[2])...)
+}
+
+func NewRegionResource() resource.Resource {
+ return ®ionResource{}
+}
diff --git a/pkg/provider/resource_region_test.go b/pkg/provider/resource_region_test.go
index 30303658..6ef15c68 100644
--- a/pkg/provider/resource_region_test.go
+++ b/pkg/provider/resource_region_test.go
@@ -21,6 +21,11 @@ func TestAccResourceRegion_basic(t *testing.T) {
provider = os.Getenv("BA_TF_ACC_VAR_region_provider")
regionID = os.Getenv("BA_TF_ACC_VAR_region_region_id")
+ regionConfigWithDefault = `resource "biganimal_region" "this" {
+ project_id = "%s"
+ cloud_provider = "%s"
+ region_id = "%s"
+}`
regionConfig = `resource "biganimal_region" "this" {
status = "%s"
project_id = "%s"
@@ -34,10 +39,10 @@ func TestAccResourceRegion_basic(t *testing.T) {
testAccPreCheck(t)
testAccResourcePreCheck(t, "region", acc_env_vars_checklist)
},
- ProviderFactories: testAccProviderFactories,
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
- Config: fmt.Sprintf(regionConfig, api.REGION_ACTIVE, projectID, provider, regionID),
+ Config: fmt.Sprintf(regionConfigWithDefault, projectID, provider, regionID),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("biganimal_region.this", "status", api.REGION_ACTIVE),
),
diff --git a/pkg/provider/utils.go b/pkg/provider/utils.go
index 583fb65d..94388afc 100644
--- a/pkg/provider/utils.go
+++ b/pkg/provider/utils.go
@@ -4,34 +4,58 @@ import (
"errors"
"github.com/EnterpriseDB/terraform-provider-biganimal/pkg/api"
+ frameworkdiag "github.com/hashicorp/terraform-plugin-framework/diag"
- "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+ sdkdiag "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
)
-func fromBigAnimalErr(err error) diag.Diagnostics {
+func fromBigAnimalErr(err error) sdkdiag.Diagnostics {
if err == nil {
return nil
}
var baError *api.BigAnimalError
if errors.As(err, &baError) {
- return diag.Diagnostics{
- diag.Diagnostic{
- Severity: diag.Error,
+ return sdkdiag.Diagnostics{
+ sdkdiag.Diagnostic{
+ Severity: sdkdiag.Error,
Summary: baError.Error(),
Detail: baError.GetDetails(),
},
}
}
- return diag.FromErr(err)
+ return sdkdiag.FromErr(err)
}
-func unsupportedWarning(message string) diag.Diagnostics {
- return diag.Diagnostics{
- diag.Diagnostic{
- Severity: diag.Warning,
+func unsupportedWarning(message string) sdkdiag.Diagnostics {
+ return sdkdiag.Diagnostics{
+ sdkdiag.Diagnostic{
+ Severity: sdkdiag.Warning,
Summary: "Unsupported",
Detail: message,
},
}
}
+
+/*
+Please use this function for error check after client API calls.
+This function returns a boolean representing if passed error is as *api.BigAnimalError, for example:
+
+ r.client.Read(ctx, ...)
+ if err != nil {
+ if appendDiagFromBAErr(err){
+ return
+ }
+
+ resp.Diagnostics.AddError(summary, detail)
+ return
+ }
+*/
+func appendDiagFromBAErr(err error, diags *frameworkdiag.Diagnostics) bool {
+ var baError *api.BigAnimalError
+ if errors.As(err, &baError) {
+ diags.AddError(baError.Error(), baError.GetDetails())
+ return true
+ }
+ return false
+}
diff --git a/pkg/provider/validators.go b/pkg/provider/validators.go
index d773c4aa..f59c1631 100644
--- a/pkg/provider/validators.go
+++ b/pkg/provider/validators.go
@@ -5,10 +5,11 @@ import (
"regexp"
"strings"
- "github.com/google/uuid"
-
"github.com/aws/aws-sdk-go/aws/arn"
+ "github.com/google/uuid"
"github.com/hashicorp/go-cty/cty"
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
)
@@ -28,6 +29,13 @@ func validateProjectId(v interface{}, path cty.Path) diag.Diagnostics {
return diags
}
+func ProjectIdValidator() validator.String {
+ return stringvalidator.RegexMatches(
+ regexp.MustCompile("^prj_[0-9A-Za-z_]{16}$"),
+ "Please provide a valid name for the project_id, for example: prj_abcdABCD01234567",
+ )
+}
+
func validateARN(v interface{}, _ cty.Path) diag.Diagnostics {
a, err := arn.Parse(v.(string))
if err != nil || a.Service != "iam" || !strings.HasPrefix(a.Resource, "role") {