Skip to content

Commit

Permalink
adds PITR to tembo_instance (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
shahadarsh authored Nov 3, 2023
1 parent 6e1dedc commit 7b3368d
Show file tree
Hide file tree
Showing 48 changed files with 3,172 additions and 397 deletions.
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,6 @@ jobs:
TF_ACC: "1"
TEMBO_ACCESS_TOKEN: ${{ secrets.TEMBO_ACCESS_TOKEN }}
ORG_ID: ${{ secrets.ORG_ID }}
TEMBO_HOST: ${{ secrets.TEMBO_HOST }}
run: go test -v -cover ./internal/provider/
timeout-minutes: 10
13 changes: 13 additions & 0 deletions docs/resources/instance.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ output "instance" {
- `ip_allow_list` (List of String) Allowed IP list
- `postgres_configs` (Attributes List) Postgres configs (see [below for nested schema](#nestedatt--postgres_configs))
- `replicas` (Number) Instance replicas
- `restore` (Attributes) (see [below for nested schema](#nestedatt--restore))
- `trunk_installs` (Attributes List) Trunk installs (see [below for nested schema](#nestedatt--trunk_installs))

### Read-Only
Expand Down Expand Up @@ -162,6 +163,18 @@ Optional:
- `value` (String) Postgres config value


<a id="nestedatt--restore"></a>
### Nested Schema for `restore`

Required:

- `instance_id` (String)

Optional:

- `recovery_target_time` (String)


<a id="nestedatt--trunk_installs"></a>
### Nested Schema for `trunk_installs`

Expand Down
6 changes: 5 additions & 1 deletion examples/resource-creation/main.tf
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
resource "tembo_instance" "test_db" {
instance_name = "tfprovider-4"
instance_name = "tfprovider-30"
org_id = "org_2UJ2WPYFsE42Cos6mlmIuwIIJ4V"
cpu = "1"
stack_type = "Standard"
Expand Down Expand Up @@ -73,6 +73,10 @@ resource "tembo_instance" "test_db" {
}
}
}
restore = {
instance_id = "inst_1699028306429_Jq89Ty_3"
recovery_target_time = "2023-11-03T16:20:00Z"
}
}

data "tembo_instance_secrets" "test" {
Expand Down
149 changes: 113 additions & 36 deletions internal/provider/instance_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ type temboInstanceResourceModel struct {
Extensions []Extension `tfsdk:"extensions"`
IpAllowList []types.String `tfsdk:"ip_allow_list"`
ConnectionPooler *ConnectionPooler `tfsdk:"connection_pooler"`
Restore *Restore `tfsdk:"restore"`
}

type Restore struct {
InstanceId types.String `tfsdk:"instance_id"`
RecoveryTargetTime types.String `tfsdk:"recovery_target_time"`
}

type ConnectionPooler struct {
Expand Down Expand Up @@ -300,6 +306,17 @@ func (r *temboInstanceResource) Schema(_ context.Context, _ resource.SchemaReque
},
Optional: true,
},
"restore": schema.SingleNestedAttribute{
Attributes: map[string]schema.Attribute{
"instance_id": schema.StringAttribute{
Required: true,
},
"recovery_target_time": schema.StringAttribute{
Optional: true,
},
},
Optional: true,
},
},
}
}
Expand All @@ -314,7 +331,52 @@ func (r *temboInstanceResource) Create(ctx context.Context, req resource.CreateR
return
}

// Call API to Create Tembo Instance
var instanceId string
var hasError bool

isRestoreMode := (plan.Restore != nil)

if isRestoreMode {
ctx, instanceId, hasError = restoreInstance(plan, ctx, r, resp)
} else {
ctx, instanceId, hasError = createInstance(plan, ctx, r, resp)
}

if hasError {
return
}

// Wait until it's created.
for {
latestInstance, err := getInstance(r, ctx, plan.OrgId.ValueString(), instanceId, &resp.Diagnostics)

if err != nil {
return
}

if latestInstance.GetState() == temboclient.ERROR ||
latestInstance.GetState() == temboclient.UP {

// Map response body to schema and populate Computed attribute values
setTemboInstanceResourceModel(&plan, &latestInstance, true, isRestoreMode, &resp.Diagnostics)

break
}

time.Sleep(10 * time.Second)
}

// Set state to fully populated data
diags = resp.State.Set(ctx, plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}

func createInstance(plan temboInstanceResourceModel,
ctx context.Context, r *temboInstanceResource,
resp *resource.CreateResponse) (context.Context, string, bool) {
createInstance := *temboclient.NewCreateInstance(
temboclient.Cpu(plan.CPU.ValueString()),
temboclient.Environment(plan.Environment.ValueString()),
Expand All @@ -341,7 +403,6 @@ func (r *temboInstanceResource) Create(ctx context.Context, req resource.CreateR
createInstance.SetConnectionPooler(*getConnectionPooler(plan.ConnectionPooler))
}

// TODO: Figure out a better way to set this so it doesn't have to be be called in each method.
ctx = context.WithValue(ctx, temboclient.ContextAccessToken, r.temboInstanceConfig.accessToken)

instanceRequest := r.temboInstanceConfig.client.InstanceApi.CreateInstance(ctx,
Expand All @@ -353,35 +414,45 @@ func (r *temboInstanceResource) Create(ctx context.Context, req resource.CreateR
"Error creating Tembo Instance:",
"Could not create Tembo Instance, unexpected error: "+getErrorFromResponse(response),
)
return
return nil, "", true
}

// Wait until it's created.
for {
latestInstance, err := getInstance(r, ctx, plan.OrgId.ValueString(), instance.GetInstanceId(), &resp.Diagnostics)
return ctx, instance.GetInstanceId(), false
}

if err != nil {
return
}
func restoreInstance(plan temboInstanceResourceModel,
ctx context.Context, r *temboInstanceResource,
resp *resource.CreateResponse) (context.Context, string, bool) {
restoreInstance := *temboclient.NewRestoreInstance(
plan.InstanceName.ValueString(),
*temboclient.NewRestore(plan.Restore.InstanceId.ValueString()),
)

if latestInstance.GetState() == temboclient.ERROR ||
latestInstance.GetState() == temboclient.UP {
restoreInstance.SetCpu(temboclient.Cpu(plan.CPU.ValueString()))
restoreInstance.SetEnvironment(temboclient.Environment(plan.Environment.ValueString()))
restoreInstance.SetMemory(temboclient.Memory(plan.Memory.ValueString()))
restoreInstance.SetStorage(temboclient.Storage(plan.Storage.ValueString()))

// Map response body to schema and populate Computed attribute values
setTemboInstanceResourceModel(&plan, &latestInstance, true, &resp.Diagnostics)
restoreInstance.SetExtraDomainsRw(getStringArray(plan.ExtraDomainsRw))

break
}
/*if plan.ConnectionPooler != nil {
restoreInstance.SetConnectionPooler(*getConnectionPooler(plan.ConnectionPooler))
}*/

time.Sleep(10 * time.Second)
}
ctx = context.WithValue(ctx, temboclient.ContextAccessToken, r.temboInstanceConfig.accessToken)

// Set state to fully populated data
diags = resp.State.Set(ctx, plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
instanceRequest := r.temboInstanceConfig.client.InstanceApi.RestoreInstance(ctx,
plan.OrgId.ValueString())

instance, response, err := instanceRequest.RestoreInstance(restoreInstance).Execute()
if err != nil {
resp.Diagnostics.AddError(
"Error restoring Tembo Instance:",
"Could not restore Tembo Instance, unexpected error: "+getErrorFromResponse(response),
)
return nil, "", true
}
return ctx, instance.GetInstanceId(), false
}

// Read refreshes the Terraform state with the latest data.
Expand All @@ -406,7 +477,7 @@ func (r *temboInstanceResource) Read(ctx context.Context, req resource.ReadReque
}

// Overwrite items with refreshed state
setTemboInstanceResourceModel(state, instance, false, &resp.Diagnostics)
setTemboInstanceResourceModel(state, instance, false, false, &resp.Diagnostics)

// Set refreshed state
diags = resp.State.Set(ctx, &state)
Expand Down Expand Up @@ -474,7 +545,7 @@ func (r *temboInstanceResource) Update(ctx context.Context, req resource.UpdateR
updateInstance.GetReplicas() == instance.GetReplicas() &&
instance.GetState() == temboclient.UP {
// Update resource state with updated items and timestamp
setTemboInstanceResourceModel(&plan, &instance, true, &resp.Diagnostics)
setTemboInstanceResourceModel(&plan, &instance, true, false, &resp.Diagnostics)
break
}
time.Sleep(10 * time.Second)
Expand Down Expand Up @@ -535,7 +606,7 @@ func (r *temboInstanceResource) ImportState(ctx context.Context, req resource.Im
}

func setTemboInstanceResourceModel(instanceResourceModel *temboInstanceResourceModel,
instance *temboclient.Instance, isUpdateMode bool, diagnostics *diag.Diagnostics) {
instance *temboclient.Instance, isUpdateMode bool, isRestoreMode bool, diagnostics *diag.Diagnostics) {
if isUpdateMode {
instanceResourceModel.LastUpdated = types.StringValue(time.Now().Format(time.RFC850))
}
Expand All @@ -544,12 +615,10 @@ func setTemboInstanceResourceModel(instanceResourceModel *temboInstanceResourceM
instanceResourceModel.InstanceName = types.StringValue(instance.InstanceName)
instanceResourceModel.OrgId = types.StringValue(instance.GetOrganizationId())
instanceResourceModel.CPU = types.StringValue(string(instance.GetCpu()))
instanceResourceModel.StackType = types.StringValue(string(instance.StackType))
instanceResourceModel.Environment = types.StringValue(string(instance.GetEnvironment()))
instanceResourceModel.Memory = types.StringValue(string(instance.GetMemory()))
instanceResourceModel.Storage = types.StringValue(string(instance.GetStorage()))
instanceResourceModel.State = types.StringValue(string(instance.GetState()))
instanceResourceModel.Replicas = types.Int64Value(int64(instance.GetReplicas()))

if len(instance.ExtraDomainsRw) > 0 {
var localExtraDomainsRw []basetypes.StringValue
Expand All @@ -559,6 +628,23 @@ func setTemboInstanceResourceModel(instanceResourceModel *temboInstanceResourceM
instanceResourceModel.ExtraDomainsRw = localExtraDomainsRw
}

// Restore Mode only sets above fields so skipping the other fields.
if isRestoreMode {
return
}

if instance.ConnectionPooler.Get() != nil {
var localConnectionPooler ConnectionPooler
cp := instance.ConnectionPooler.Get()
localConnectionPooler.Enabled = types.BoolValue(*cp.Enabled)
localConnectionPooler.Pooler.PoolMode = types.StringValue(string(*cp.Pooler.PoolMode.Ptr()))
localConnectionPooler.Pooler.Parameters = cp.Pooler.Parameters
instanceResourceModel.ConnectionPooler = &localConnectionPooler
}

instanceResourceModel.StackType = types.StringValue(string(instance.StackType))
instanceResourceModel.Replicas = types.Int64Value(int64(instance.GetReplicas()))

if len(instance.PostgresConfigs) > 0 {
var localPGConfigs []KeyValue
for _, pgConfig := range instance.PostgresConfigs {
Expand Down Expand Up @@ -600,15 +686,6 @@ func setTemboInstanceResourceModel(instanceResourceModel *temboInstanceResourceM
}
instanceResourceModel.IpAllowList = localIpAllowList
}

if instance.ConnectionPooler.Get() != nil {
var localConnectionPooler ConnectionPooler
cp := instance.ConnectionPooler.Get()
localConnectionPooler.Enabled = types.BoolValue(*cp.Enabled)
localConnectionPooler.Pooler.PoolMode = types.StringValue(string(*cp.Pooler.PoolMode.Ptr()))
localConnectionPooler.Pooler.Parameters = cp.Pooler.Parameters
instanceResourceModel.ConnectionPooler = &localConnectionPooler
}
}

func getStringArray(inputArray []basetypes.StringValue) []string {
Expand Down
26 changes: 20 additions & 6 deletions internal/provider/temboclient/.openapi-generator/FILES
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ api_instance.go
api_stack.go
client.go
configuration.go
docs/AppConfig.md
docs/AppService.md
docs/AppServiceConfig.md
docs/AppServiceType.md
docs/AppServiceTypeOneOf.md
docs/AppType.md
docs/AppTypeOneOf.md
docs/AppTypeOneOf1.md
docs/AppTypeOneOf2.md
docs/ConnectionInfo.md
docs/ConnectionPooler.md
docs/Cpu.md
Expand All @@ -36,6 +38,7 @@ docs/Memory.md
docs/Middleware.md
docs/MiddlewareOneOf.md
docs/MiddlewareOneOf1.md
docs/MiddlewareOneOf2.md
docs/PatchInstance.md
docs/PgBouncer.md
docs/PgConfig.md
Expand All @@ -44,8 +47,12 @@ docs/PoolerTemplateSpecContainersResources.md
docs/PoolerTemplateSpecContainersResourcesClaims.md
docs/Probe.md
docs/Probes.md
docs/ReplacePathRegexConfig.md
docs/ReplacePathRegexConfigType.md
docs/Resource.md
docs/ResourceRequirements.md
docs/Restore.md
docs/RestoreInstance.md
docs/Routing.md
docs/StackApi.md
docs/StackType.md
Expand All @@ -58,10 +65,12 @@ docs/UpdateInstance.md
git_push.sh
go.mod
go.sum
model_app_config.go
model_app_service.go
model_app_service_config.go
model_app_service_type.go
model_app_service_type_one_of.go
model_app_type.go
model_app_type_one_of.go
model_app_type_one_of_1.go
model_app_type_one_of_2.go
model_connection_info.go
model_connection_pooler.go
model_cpu.go
Expand All @@ -86,6 +95,7 @@ model_memory.go
model_middleware.go
model_middleware_one_of.go
model_middleware_one_of_1.go
model_middleware_one_of_2.go
model_patch_instance.go
model_pg_bouncer.go
model_pg_config.go
Expand All @@ -94,8 +104,12 @@ model_pooler_template_spec_containers_resources.go
model_pooler_template_spec_containers_resources_claims.go
model_probe.go
model_probes.go
model_replace_path_regex_config.go
model_replace_path_regex_config_type.go
model_resource.go
model_resource_requirements.go
model_restore.go
model_restore_instance.go
model_routing.go
model_stack_type.go
model_state.go
Expand Down
Loading

0 comments on commit 7b3368d

Please sign in to comment.