Skip to content

Commit

Permalink
refactor: rewrite resource_region with terraform-plugin-framework
Browse files Browse the repository at this point in the history
  • Loading branch information
YanniHu1996 committed May 15, 2023
1 parent e9233f9 commit 08d6d28
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 2 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ require (
github.com/hashicorp/logutils v1.0.0 // indirect
github.com/hashicorp/terraform-exec v0.18.1 // indirect
github.com/hashicorp/terraform-json v0.16.0 // indirect
github.com/hashicorp/terraform-plugin-framework-timeouts v0.3.1 // indirect
github.com/hashicorp/terraform-plugin-framework-validators v0.10.0 // indirect
github.com/hashicorp/terraform-registry-address v0.1.0 // indirect
github.com/hashicorp/terraform-svchost v0.1.0 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ github.com/hashicorp/terraform-plugin-docs v0.14.2-0.20230330141128-e8f8eb1f6dbb
github.com/hashicorp/terraform-plugin-docs v0.14.2-0.20230330141128-e8f8eb1f6dbb/go.mod h1:1YAwCOLHQLQUkM+rPf1+tCayEK92kdyLIfzSfEDe6og=
github.com/hashicorp/terraform-plugin-framework v1.2.0 h1:MZjFFfULnFq8fh04FqrKPcJ/nGpHOvX4buIygT3MSNY=
github.com/hashicorp/terraform-plugin-framework v1.2.0/go.mod h1:nToI62JylqXDq84weLJ/U3umUsBhZAaTmU0HXIVUOcw=
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.14.3 h1:nlnJ1GXKdMwsC8g1Nh05tK2wsC3+3BL/DBBxFEki+j0=
github.com/hashicorp/terraform-plugin-go v0.14.3/go.mod h1:7ees7DMZ263q8wQ6E4RdIdR6nHHJtrdt4ogX5lPkX1A=
github.com/hashicorp/terraform-plugin-log v0.8.0 h1:pX2VQ/TGKu+UU1rCay0OlzosNKe4Nz1pepLXj95oyy0=
Expand Down
28 changes: 28 additions & 0 deletions pkg/provider/default.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package provider

import (
"context"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults"
"github.com/hashicorp/terraform-plugin-framework/types"
)

type defaultString struct {
Desc string
Default string
}

func DefaultString(desc string, Default string) *defaultString {
return &defaultString{Desc: desc, Default: Default}
}

func (d defaultString) Description(_ context.Context) string {
return d.Desc
}

func (d defaultString) MarkdownDescription(_ context.Context) string {
return d.Desc
}

func (d defaultString) DefaultString(ctx context.Context, request defaults.StringRequest, response *defaults.StringResponse) {
response.PlanValue = types.StringValue(d.Default)
}
186 changes: 184 additions & 2 deletions pkg/provider/resource_region.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,22 @@ import (
"context"
"errors"
"fmt"
"time"

"github.com/EnterpriseDB/terraform-provider-biganimal/pkg/api"
"github.com/EnterpriseDB/terraform-provider-biganimal/pkg/utils"
"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
fdiag "github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/resource"
fschema "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/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"

"time"
)

// RegionResource is a struct to namespace all the functions
Expand Down Expand Up @@ -186,3 +194,177 @@ func (r *RegionResource) retryFunc(ctx context.Context, d *schema.ResourceData,
return nil
}
}

type regionResource struct {
client *api.API
}

func (r regionResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = fschema.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]fschema.Block{
"timeouts": timeouts.Block(ctx,
timeouts.Opts{Create: true, Delete: true, Update: true}),
},

Attributes: map[string]fschema.Attribute{
"cloud_provider": fschema.StringAttribute{
MarkdownDescription: "Cloud provider. For example, \"aws\" or \"azure\".",
Required: true,
},
"project_id": fschema.StringAttribute{
MarkdownDescription: "BigAnimal Project ID.",
Required: true,
Validators: []validator.String{
ProjectIdValidator(),
},
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"region_id": fschema.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,
},
"name": fschema.StringAttribute{
MarkdownDescription: "Region name of the region. For example, \"Germany West Central\" or \"EU West 1\".",
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"status": fschema.StringAttribute{
MarkdownDescription: "Region status of the region. For example, \"ACTIVE\", \"INACTIVE\", or \"SUSPENDED\".",
Optional: true,
Default: DefaultString("The default of region desired status", api.REGION_ACTIVE),
},
"continent": fschema.StringAttribute{
MarkdownDescription: "Continent that region belongs to. For example, \"Asia\", \"Australia\", or \"Europe\".",
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
},
}
}

type Region struct {
ProjectID string `tfsdk:"project_id"`
CloudProvider string `tfsdk:"cloud_provider"`
RegionID string `tfsdk:"region_id"`
Name string `tfsdk:"name"`
Status string `tfsdk:"status"`
Continent string `tfsdk:"continent"`

Timeouts timeouts.Value `tfsdk:"timeouts"`
}

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

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
}

diags = r.update(ctx, config, resp.State)
resp.Diagnostics.Append(diags...)
return
}

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
}

resp.Diagnostics.Append(r.read(ctx, state, resp.State)...)
}

func (r regionResource) read(ctx context.Context, region Region, state tfsdk.State) fdiag.Diagnostics {
read, err := r.client.RegionClient().Read(ctx, region.ProjectID, region.CloudProvider, region.RegionID)
if err != nil {
return fromErr(err, "Error reading region %v", region.RegionID)
}

region.Name = read.Name
region.Status = read.Status
region.Continent = read.Continent
return state.Set(ctx, &region)
}

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
}

resp.Diagnostics.Append(r.update(ctx, plan, resp.State)...)
}

func (r regionResource) update(ctx context.Context, region Region, state tfsdk.State) fdiag.Diagnostics {
current, err := r.client.RegionClient().Read(ctx, region.ProjectID, region.CloudProvider, region.RegionID)
if err != nil {
return fromErr(err, "Error reading region %v", region.RegionID)
}
if current.Status == region.Status { // no change, exit early
return nil
}

tflog.Debug(ctx, fmt.Sprintf("updating region from %s to %s", current.Status, region.Status))

if err := r.client.RegionClient().Update(ctx, region.Status, region.ProjectID, region.CloudProvider, region.RegionID); err != nil {
return fromErr(err, "Error updating region %v", region.RegionID)
}

timeout, diagnostics := region.Timeouts.Create(ctx, 60*time.Minute)
if diagnostics != nil {
return diagnostics
}

err = retry.RetryContext(
ctx,
timeout-time.Minute,
r.retryFunc(ctx, region))
if err != nil {
return fromErr(err, "")
}

return r.read(ctx, region, state)
}

func (r regionResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
//TODO implement me
panic("implement me")
}

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

r.client = req.ProviderData.(*api.API)
}

func (r regionResource) retryFunc(ctx context.Context, region Region) retry.RetryFunc {
return func() *retry.RetryError {
curr, err := r.client.RegionClient().Read(ctx, region.ProjectID, region.CloudProvider, region.RegionID)
if err != nil {
return retry.NonRetryableError(fmt.Errorf("error describing instance: %s", err))
}

if curr.Status != region.Status {
return retry.RetryableError(errors.New("operation incomplete"))
}
return nil
}
}
11 changes: 11 additions & 0 deletions pkg/provider/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package provider

import (
"errors"
"fmt"
"github.com/EnterpriseDB/terraform-provider-biganimal/pkg/api"
diag2 "github.com/hashicorp/terraform-plugin-framework/diag"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
)
Expand Down Expand Up @@ -34,3 +36,12 @@ func unsupportedWarning(message string) diag.Diagnostics {
},
}
}

func fromErr(err error, summary string, args ...any) diag2.Diagnostics {
summary = fmt.Sprintf(summary, args...)
return diag2.Diagnostics{
diag2.NewErrorDiagnostic(
summary, err.Error(),
),
}
}
9 changes: 9 additions & 0 deletions pkg/provider/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package provider
import (
"fmt"
"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"regexp"
"strings"

Expand All @@ -27,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") {
Expand Down

0 comments on commit 08d6d28

Please sign in to comment.