Skip to content

Commit

Permalink
feat: restore pg cluster implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
wai-wong-edb committed Dec 1, 2023
1 parent b045931 commit 85bede1
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 15 deletions.
67 changes: 63 additions & 4 deletions pkg/api/cluster_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ func (c ClusterClient) Create(ctx context.Context, projectId string, model any)

url := fmt.Sprintf("projects/%s/clusters", projectId)
body, err := c.doRequest(ctx, http.MethodPost, url, bytes.NewBuffer(b))

if err != nil {
return "", err
}
Expand All @@ -49,6 +48,22 @@ func (c ClusterClient) Create(ctx context.Context, projectId string, model any)
return response.Data.ClusterId, err
}

func (c ClusterClient) ReadDeletedCluster(ctx context.Context, projectId, id string) (*models.Cluster, error) {
response := struct {
Data models.Cluster `json:"data"`
}{}

url := fmt.Sprintf("projects/%s/deleted-clusters/%s", projectId, id)
body, err := c.doRequest(ctx, http.MethodGet, url, nil)
if err != nil {
return &response.Data, err
}

err = json.Unmarshal(body, &response)

return &response.Data, err
}

func (c ClusterClient) Read(ctx context.Context, projectId, id string) (*models.Cluster, error) {
response := struct {
Data models.Cluster `json:"data"`
Expand All @@ -73,18 +88,18 @@ func (c ClusterClient) ReadByName(ctx context.Context, projectId, name string, m
url := fmt.Sprintf("projects/%s/clusters?name=%s", projectId, name)
body, err := c.doRequest(ctx, http.MethodGet, url, nil)
if err != nil {
return &models.Cluster{}, err
return nil, err
}

if err := json.Unmarshal(body, &clusters); err != nil {
return &models.Cluster{}, err
return nil, err
}

if len(clusters.Data) != 1 {
if most_recent {
sort.Slice(clusters.Data, func(i, j int) bool { return clusters.Data[i].CreatedAt.Seconds > clusters.Data[j].CreatedAt.Seconds })
} else {
return &models.Cluster{}, ErrorClustersSameName
return nil, ErrorClustersSameName
}
}

Expand Down Expand Up @@ -175,3 +190,47 @@ func (c ClusterClient) GetPeAllowedPrincipalIds(ctx context.Context, projectID s
}
return &response.Data, nil
}

func (c ClusterClient) RestoreCluster(ctx context.Context, projectId, clusterId string, model models.RestoreCluster) (string, error) {
response := struct {
Data struct {
ClusterId string `json:"clusterId"`
} `json:"data"`
}{}

b, err := json.Marshal(model)
if err != nil {
return "", err
}

url := fmt.Sprintf("projects/%s/clusters/%s/restore", projectId, clusterId)
body, err := c.doRequest(ctx, http.MethodPost, url, bytes.NewBuffer(b))
if err != nil {
return "", err
}

err = json.Unmarshal(body, &response)
return response.Data.ClusterId, err
}

func (c ClusterClient) RestoreClusterFromDeleted(ctx context.Context, projectId, clusterId string, model models.RestoreCluster) (string, error) {
response := struct {
Data struct {
ClusterId string `json:"clusterId"`
} `json:"data"`
}{}

b, err := json.Marshal(model)
if err != nil {
return "", err
}

url := fmt.Sprintf("projects/%s/deleted-clusters/%s/restore", projectId, clusterId)
body, err := c.doRequest(ctx, http.MethodPost, url, bytes.NewBuffer(b))
if err != nil {
return "", err
}

err = json.Unmarshal(body, &response)
return response.Data.ClusterId, err
}
27 changes: 27 additions & 0 deletions pkg/models/restore_cluster.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package models

import (
commonApi "github.com/EnterpriseDB/terraform-provider-biganimal/pkg/models/common/api"
)

type RestoreCluster struct {
AllowedIpRanges *[]AllowedIpRange `json:"allowedIpRanges,omitempty"`
BackupRetentionPeriod *string `json:"backupRetentionPeriod,omitempty"`
ClusterArchitecture *Architecture `json:"clusterArchitecture,omitempty" mapstructure:"cluster_architecture"`
ClusterName *string `json:"clusterName,omitempty"`
ClusterType *string `json:"clusterType,omitempty"`
CSPAuth *bool `json:"cspAuth,omitempty"`
InstanceType *InstanceType `json:"instanceType,omitempty"`
Password *string `json:"password,omitempty"`
PgConfig *[]KeyValue `json:"pgConfig,omitempty"`
Phase *string `json:"phase,omitempty"`
ReadOnlyConnections *bool `json:"readOnlyConnections,omitempty"`
Region *Region `json:"region,omitempty"`
ResizingPvc []string `json:"resizingPvc,omitempty"`
Storage *Storage `json:"storage,omitempty"`
MaintenanceWindow *commonApi.MaintenanceWindow `json:"maintenanceWindow,omitempty"`
ServiceAccountIds *[]string `json:"serviceAccountIds,omitempty"`
PeAllowedPrincipalIds *[]string `json:"peAllowedPrincipalIds,omitempty"`
SuperuserAccess *bool `json:"superuserAccess,omitempty"`
RestorePoint *string `json:"selectedRestorePointInTime,omitempty"`
}
43 changes: 43 additions & 0 deletions pkg/plan_modifier/restore_cluster_id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package plan_modifier

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
)

// CustomRestoreClusterId returns a plan modifier that copies a known prior state
// value into the planned value. Use this when it is known that an unconfigured
// value will remain the same after a resource update.
//
// To prevent Terraform errors, the framework automatically sets unconfigured
// and Computed attributes to an unknown value "(known after apply)" on update.
// Using this plan modifier will instead display the prior state value in the
// plan, unless a prior plan modifier adjusts the value.
func CustomRestoreClusterId() planmodifier.String {
return customRestoreClusterIdModifier{}
}

// customRestoreClusterIdModifier implements the plan modifier.
type customRestoreClusterIdModifier struct{}

// Description returns a human-readable description of the plan modifier.
func (m customRestoreClusterIdModifier) Description(_ context.Context) string {
return "Once set, the value of this attribute in state will not change."
}

// MarkdownDescription returns a markdown description of the plan modifier.
func (m customRestoreClusterIdModifier) MarkdownDescription(_ context.Context) string {
return "Once set, the value of this attribute in state will not change."
}

// PlanModifyString implements the plan modification logic.
func (m customRestoreClusterIdModifier) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) {
if !req.PlanValue.IsNull() {
resp.Diagnostics.AddWarning(
"You are restoring cluster",
fmt.Sprint("You are restoring a cluster. After restoring the cluster, please remove field 'restore_cluster_id' and optionally remove fields 'restore_from_deleted' and 'restore_point' from the config"),
)
}
}
91 changes: 80 additions & 11 deletions pkg/provider/resource_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,12 @@ type ClusterResourceModel struct {
ServiceAccountIds types.Set `tfsdk:"service_account_ids"`
PeAllowedPrincipalIds types.Set `tfsdk:"pe_allowed_principal_ids"`
SuperuserAccess types.Bool `tfsdk:"superuser_access"`
FromDeleted *bool `tfsdk:"from_deleted"`
ImportFromDeleted types.Bool `tfsdk:"import_from_deleted"`
RestoreFromDeleted *bool `tfsdk:"restore_from_deleted"`
RestoreClusterId *string `tfsdk:"restore_cluster_id"`
RestorePoint types.String `tfsdk:"restore_point"`
Pgvector types.Bool `tfsdk:"pgvector"`
MostRecent types.Bool `tfsdk:"most_recent"`

Timeouts timeouts.Value `tfsdk:"timeouts"`
}
Expand Down Expand Up @@ -238,6 +242,10 @@ func (c *clusterResource) Schema(ctx context.Context, req resource.SchemaRequest
MarkdownDescription: "Name of the cluster.",
Required: true,
},
"most_recent": schema.BoolAttribute{
MarkdownDescription: "Show the most recent cluster when there are multiple clusters with the same name.",
Optional: true,
},
"phase": schema.StringAttribute{
MarkdownDescription: "Current phase of the cluster.",
Computed: true,
Expand Down Expand Up @@ -393,10 +401,23 @@ func (c *clusterResource) Schema(ctx context.Context, req resource.SchemaRequest
Optional: true,
Computed: true,
},
"from_deleted": schema.BoolAttribute{
"import_from_deleted": schema.BoolAttribute{
Description: "Used by import function only to import a deleted cluster",
Optional: true,
},
"restore_from_deleted": schema.BoolAttribute{
Description: "For restoring a cluster. Specifies if the cluster you want to restore is deleted",
Optional: true,
},
"restore_cluster_id": schema.StringAttribute{
Description: "For restoring a cluster. Specifies the cluster id to restore",
Optional: true,
PlanModifiers: []planmodifier.String{plan_modifier.CustomRestoreClusterId()},
},
"restore_point": schema.StringAttribute{
Description: "For restoring a cluster. Specifies restore point e.g. 2006-01-02T15:04:05-0700. Leave empty to restore from latest point",
Optional: true,
},
"pgvector": schema.BoolAttribute{
MarkdownDescription: "Is pgvector extension enabled. Adds support for vector storage and vector similarity search to Postgres.",
Optional: true,
Expand Down Expand Up @@ -424,12 +445,45 @@ func (c *clusterResource) Create(ctx context.Context, req resource.CreateRequest
return
}

clusterId, err := c.client.Create(ctx, config.ProjectId, clusterModel)
if err != nil {
if !appendDiagFromBAErr(err, &resp.Diagnostics) {
resp.Diagnostics.AddError("Error creating cluster API request", err.Error())
var clusterId string

if config.RestoreClusterId != nil {
var restoreModel models.RestoreCluster
err := utils.CopyObjectJson(clusterModel, &restoreModel)
if err != nil {
if !appendDiagFromBAErr(err, &resp.Diagnostics) {
resp.Diagnostics.AddError("Error copy object json in restore cluster API request", err.Error())
}
return
}
return

if config.RestoreFromDeleted != nil && *config.RestoreFromDeleted {
clusterId, err = c.client.RestoreClusterFromDeleted(ctx, config.ProjectId, *config.RestoreClusterId, restoreModel)
if err != nil {
if !appendDiagFromBAErr(err, &resp.Diagnostics) {
resp.Diagnostics.AddError("Error restore cluster API request", err.Error())
}
return
}
} else {
clusterId, err = c.client.RestoreCluster(ctx, config.ProjectId, *config.RestoreClusterId, restoreModel)
if err != nil {
if !appendDiagFromBAErr(err, &resp.Diagnostics) {
resp.Diagnostics.AddError("Error restore cluster API request", err.Error())
}
return
}
}

} else {
clusterId, err = c.client.Create(ctx, config.ProjectId, clusterModel)
if err != nil {
if !appendDiagFromBAErr(err, &resp.Diagnostics) {
resp.Diagnostics.AddError("Error creating cluster API request", err.Error())
}
return
}

}

config.ClusterId = &clusterId
Expand Down Expand Up @@ -537,7 +591,7 @@ func (c *clusterResource) Delete(ctx context.Context, req resource.DeleteRequest

func (c *clusterResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
idParts := strings.Split(req.ID, "/")
if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" {
if len(idParts) < 2 || idParts[0] == "" || idParts[1] == "" {
resp.Diagnostics.AddError(
"Unexpected Import Identifier",
fmt.Sprintf("Expected import identifier with format: project_id/cluster_id. Got: %q", req.ID),
Expand All @@ -547,12 +601,27 @@ func (c *clusterResource) ImportState(ctx context.Context, req resource.ImportSt

resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("cluster_id"), idParts[1])...)

if len(idParts) > 2 && idParts[2] == "import-from-deleted" {
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("import_from_deleted"), true)...)
}
}

func (c *clusterResource) read(ctx context.Context, clusterResource *ClusterResourceModel) error {
cluster, err := c.client.Read(ctx, clusterResource.ProjectId, *clusterResource.ClusterId)
if err != nil {
return err
var cluster *models.Cluster

if clusterResource.ImportFromDeleted.ValueBool() {
deletedCluster, err := c.client.ReadDeletedCluster(ctx, clusterResource.ProjectId, *clusterResource.ClusterId)
if err != nil {
return err
}
cluster = deletedCluster
} else {
existingCluster, err := c.client.Read(ctx, clusterResource.ProjectId, *clusterResource.ClusterId)
if err != nil {
return err
}
cluster = existingCluster
}

connection, err := c.client.ConnectionString(ctx, clusterResource.ProjectId, *clusterResource.ClusterId)
Expand Down

0 comments on commit 85bede1

Please sign in to comment.