You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
terraform-provider-dynamix/internal/service/cloudapi/bservice/utilities/utility_resource_bservice.go

387 lines
14 KiB

package utilities
import (
"context"
"fmt"
"strconv"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-log/tflog"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi/bservice"
"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/client"
"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/service/cloudapi/bservice/models"
"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/service/cloudapi/ic"
"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/status"
)
// BServiceResourceCheckPresence checks if BService with serviceID exists
func BServiceResourceCheckPresence(ctx context.Context, serviceID uint64, c *client.Client) (*bservice.RecordBasicService, error) {
tflog.Info(ctx, fmt.Sprintf("BServiceResourceCheckPresence: Get info about service with ID - %v", serviceID))
record, err := c.CloudAPI().BService().Get(ctx, bservice.GetRequest{ServiceID: serviceID})
if err != nil {
return nil, fmt.Errorf("BServiceResourceCheckPresence: cannot get info about resource with error: %w", err)
}
tflog.Info(ctx, "BServiceResourceCheckPresence: response from CloudAPI().BService().Get", map[string]any{"service_id": serviceID, "response": record})
return record, err
}
// BServiceReadStatus loads BService resource by its id, gets it current status. Performs restore and enable if needed for
// Deleted status.
// In case of failure returns errors.
func BSerivceReadStatus(ctx context.Context, state *models.RecordBasicServiceResourceModel, c *client.Client) diag.Diagnostics {
tflog.Info(ctx, "BSerivceReadStatus: Read status resource with ID", map[string]any{"resource_id": state.ID.ValueString()})
diags := diag.Diagnostics{}
resourceId, err := strconv.ParseUint(state.ID.ValueString(), 10, 64)
if err != nil {
diags.AddError("BSerivceReadStatus: Cannot parse resource ID from state", err.Error())
return diags
}
record, err := BServiceResourceCheckPresence(ctx, resourceId, c)
if err != nil {
diags.AddError("BSerivceReadStatus: Unable to Read bservice before status check", err.Error())
return diags
}
// check resource status
switch record.Status {
case status.Disabled:
tflog.Info(ctx, "The BService is in status Disabled, troubles may occur with update. Please, enable BService first.")
case status.Modeled:
diags.AddError("The basic service is in status: %s, please, contact support for more information", record.Status)
case status.Disabling:
tflog.Info(ctx, fmt.Sprintf("The basic service is in status: %s, troubles can occur with the update.", record.Status))
case status.Deleted:
restore := state.Restore.ValueBool()
if state.Restore.IsNull() {
restore = true
} // default true
if restore {
// attempt to restore bservice
tflog.Info(ctx, "BServiceReadStatus: BService with status.Deleted is being read, attempt to restore it", map[string]any{
"BService": state.ID.ValueString(),
"status": record.Status})
diags.Append(restoreBservice(ctx, resourceId, c)...)
if diags.HasError() {
tflog.Error(ctx, "BServiceReadStatus: cannot restore BService")
return diags
}
tflog.Info(ctx, "BServiceReadStatus: BService restored successfully", map[string]any{"service_id": resourceId})
} else {
tflog.Info(ctx, "BServiceReadStatus: BService is i status Deleted but restore is not specified")
}
case status.Destroyed:
diags.AddError(
"BSerivceReadStatus: BService is in status Destroyed",
fmt.Sprintf("the resource with bservice_id %d cannot be read or updated because it has been destroyed", resourceId),
)
return diags
case status.Destroying:
diags.AddError(
"BSerivceReadStatus: BService is in progress with status Destroying",
fmt.Sprintf("the resource with bservice_id %d cannot be read or updated because it is currently being destroyed", resourceId),
)
return diags
}
return nil
}
func BServiceResourceCreate(ctx context.Context, plan *models.RecordBasicServiceResourceModel, c *client.Client) (*uint64, diag.Diagnostics) {
tflog.Info(ctx, "Start BServiceResourceCreate", map[string]any{"service_id": plan.ServiceId.ValueInt64()})
diags := diag.Diagnostics{}
err := ic.ExistRG(ctx, uint64(plan.RGID.ValueInt64()), c)
if err != nil {
diags.AddError(fmt.Sprintf("resourceBasicServiceCreate: can't create basic service because RGID %d is not allowed or does not exist", plan.RGID.ValueInt64()), err.Error())
return nil, diags
}
req := bservice.CreateRequest{}
req.Name = plan.Name.ValueString()
req.RGID = uint64(plan.RGID.ValueInt64())
if !plan.SSHKey.IsNull() {
req.SSHKey = plan.SSHKey.ValueString()
}
if !plan.SSHUser.IsNull() {
req.SSHUser = plan.SSHUser.ValueString()
}
// Make request and get response
serviceId, err := c.CloudAPI().BService().Create(ctx, req)
if err != nil {
tflog.Error(ctx, "Error response for create bservice", map[string]any{"error": err.Error()})
diags.AddError("Unable to Create bservice", err.Error())
return nil, diags
}
plan.ID = types.StringValue(strconv.Itoa(int(serviceId)))
enable := plan.Enable.ValueBool()
if enable && (plan.Status.ValueString() == status.Disabled || plan.Status.ValueString() == status.Created) {
tflog.Info(ctx, "resourceBasicServiceCreate: before calling CloudAPI().BService().Enable", map[string]any{"service_id": serviceId})
res, err := c.CloudAPI().BService().Enable(ctx, bservice.EnableRequest{ServiceID: serviceId})
if err != nil {
diags.AddWarning(
"resourceBasicServiceCreate: cannot enable BService",
err.Error(),
)
return &serviceId, diags
}
tflog.Info(ctx, "resourceBasicServiceCreate: response from CloudAPI().BService().Enable", map[string]any{"service_id": serviceId, "response": res})
return &serviceId, diags
}
if plan.Start.ValueBool() {
if !enable {
diags.AddWarning(
"can not start bservice that is not enabled. Set enable = true and start = true to enable and start bservice",
fmt.Sprintf("service_id: %v", serviceId),
)
return &serviceId, diags
}
_, err := c.CloudAPI().BService().Start(ctx, bservice.StartRequest{
ServiceID: serviceId,
})
if err != nil {
diags.AddWarning(
"resourceBasicServiceCreate: cannot start BService",
err.Error(),
)
return &serviceId, diags
}
}
tflog.Info(ctx, "End resourceBasicServiceCreate", map[string]any{"service_id": serviceId})
return &serviceId, diags
}
// EnableDisableBService performs BService Enable/Disable request.
// Returns error in case of failures.
func EnableDisableBService(ctx context.Context, plan *models.RecordBasicServiceResourceModel, c *client.Client) diag.Diagnostics {
tflog.Info(ctx, "Start EnableDisableBService", map[string]any{"service_id": plan.ID.ValueString()})
diags := diag.Diagnostics{}
serviceID, err := strconv.Atoi(plan.ID.ValueString())
if err != nil {
diags.AddError("EnableDisableBService: Cannot parse ID from state", err.Error())
return diags
}
if plan.Enable.ValueBool() {
tflog.Info(ctx, "EnableDisableBService: before calling CloudAPI().BService().Enable", map[string]any{"service_id": serviceID})
res, err := c.CloudAPI().BService().Enable(ctx, bservice.EnableRequest{ServiceID: uint64(serviceID)})
if err != nil {
diags.AddError(
"EnableDisableBService: cannot enable BService",
err.Error(),
)
return diags
}
tflog.Info(ctx, "EnableDisableBService: response from CloudAPI().BService().Enable", map[string]any{"service_id": serviceID, "response": res})
return nil
} else {
tflog.Info(ctx, "EnableDisableBService: before calling CloudAPI().BService().Disable", map[string]any{"service_id": serviceID})
res, err := c.CloudAPI().BService().Disable(ctx, bservice.DisableRequest{ServiceID: uint64(serviceID)})
if err != nil {
diags.AddError(
"EnableDisableBService: cannot disable BService",
err.Error(),
)
return diags
}
tflog.Info(ctx, "EnableDisableBService: response from CloudAPI().BService().Disable", map[string]any{"service_id": serviceID, "response": res})
}
return nil
}
// StartStopBService performs BService Start/Stop request.
// Returns error in case of failures.
func StartStopBService(ctx context.Context, plan *models.RecordBasicServiceResourceModel, c *client.Client) diag.Diagnostics {
tflog.Info(ctx, "Start StartStopBService", map[string]any{"service_id": plan.ID.ValueString()})
diags := diag.Diagnostics{}
serviceID, err := strconv.Atoi(plan.ID.ValueString())
if err != nil {
diags.AddError("StartStopBService: Cannot parse ID from state", err.Error())
return diags
}
if plan.Start.ValueBool() {
tflog.Info(ctx, "StartStopBService: before calling CloudAPI().BService().Start", map[string]any{"service_id": serviceID})
res, err := c.CloudAPI().BService().Start(ctx, bservice.StartRequest{ServiceID: uint64(serviceID)})
if err != nil {
diags.AddError(
"StartStopBService: cannot start BService",
err.Error(),
)
return diags
}
tflog.Info(ctx, "StartStopBService: response from CloudAPI().BService().Start", map[string]any{"service_id": serviceID, "response": res})
return nil
} else {
tflog.Info(ctx, "StartStopBService: before calling CloudAPI().BService().Stop", map[string]any{"service_id": serviceID})
res, err := c.CloudAPI().BService().Stop(ctx, bservice.StopRequest{ServiceID: uint64(serviceID)})
if err != nil {
diags.AddError(
"StartStopBService: cannot stop BService",
err.Error(),
)
return diags
}
tflog.Info(ctx, "StartStopBService: response from CloudAPI().BService().Stop", map[string]any{"service_id": serviceID, "response": res})
}
return nil
}
func SnapshotsBService(ctx context.Context, oldSnapshots basetypes.ListValue, newSnapshots basetypes.ListValue, serviceID uint64, c *client.Client) diag.Diagnostics {
diags := diag.Diagnostics{}
// Handle snapshot changes in the plan
tflog.Info(ctx, "Start SnapshotsBService", map[string]any{"service_id": serviceID})
deletedSnapshots := make([]models.ItemSnapshotResourceModel, 0)
addedSnapshots := make([]models.ItemSnapshotResourceModel, 0)
updatedSnapshots := make([]models.ItemSnapshotResourceModel, 0)
oldSnapshotsList := make([]models.ItemSnapshotResourceModel, 0, len(oldSnapshots.Elements()))
newSnapshotsList := make([]models.ItemSnapshotResourceModel, 0, len(newSnapshots.Elements()))
diags.Append(oldSnapshots.ElementsAs(ctx, &oldSnapshotsList, true)...)
if diags.HasError() {
tflog.Error(ctx, "SnapshotsBService: cannot populate SnapshotsBService with plan.Snapshots object element")
return diags
}
diags.Append(newSnapshots.ElementsAs(ctx, &newSnapshotsList, true)...)
if diags.HasError() {
tflog.Error(ctx, "SnapshotsBService: cannot populate SnapshotsBService with plan.Snapshots object element")
return diags
}
for _, el := range oldSnapshotsList {
if !isContainsSnapshot(newSnapshotsList, el) {
deletedSnapshots = append(deletedSnapshots, el)
}
}
for _, el := range newSnapshotsList {
if !isContainsSnapshot(oldSnapshotsList, el) {
addedSnapshots = append(addedSnapshots, el)
} else if isRollback(oldSnapshotsList, el) {
updatedSnapshots = append(updatedSnapshots, el)
}
}
tflog.Debug(ctx, "SnapshotsBService: Snapshots to be deleted", map[string]any{"deleted_snapshots": deletedSnapshots})
tflog.Debug(ctx, "SnapshotsBService: Snapshots to be added", map[string]any{"added_snapshots": addedSnapshots})
tflog.Debug(ctx, "SnapshotsBService: Snapshots to be updated", map[string]any{"updated_snapshots": updatedSnapshots})
if len(deletedSnapshots) > 0 {
for _, snapshot := range deletedSnapshots {
req := bservice.SnapshotDeleteRequest{
ServiceID: serviceID,
Label: snapshot.Label.ValueString(),
}
_, err := c.CloudAPI().BService().SnapshotDelete(ctx, req)
if err != nil {
tflog.Error(ctx, "SnapshotsBService: Failed to delete snapshot")
return diags
}
tflog.Info(ctx, "Deleted snapshot", map[string]any{"service_id": serviceID, "label": snapshot.Label})
}
}
if len(addedSnapshots) > 0 {
for _, snapshot := range addedSnapshots {
req := bservice.SnapshotCreateRequest{
ServiceID: serviceID,
Label: snapshot.Label.ValueString(),
}
_, err := c.CloudAPI().BService().SnapshotCreate(ctx, req)
if err != nil {
tflog.Error(ctx, "SnapshotsBService: Failed to create snapshot")
return diags
}
tflog.Info(ctx, "Created snapshot", map[string]any{"service_id": serviceID, "label": snapshot.Label})
}
}
if len(updatedSnapshots) > 0 {
for _, snapshot := range updatedSnapshots {
req := bservice.SnapshotRollbackRequest{
ServiceID: serviceID,
Label: snapshot.Label.ValueString(),
}
_, err := c.CloudAPI().BService().SnapshotRollback(ctx, req)
if err != nil {
tflog.Error(ctx, "SnapshotsBService: Failed to rollback snapshot")
return diags
}
tflog.Info(ctx, "Rolled back snapshot", map[string]any{"service_id": serviceID, "label": snapshot.Label})
}
}
return nil
}
func isContainsSnapshot(els []models.ItemSnapshotResourceModel, el models.ItemSnapshotResourceModel) bool {
for _, elOld := range els {
if elOld.GUID == el.GUID {
return true
}
}
return false
}
func isRollback(els []models.ItemSnapshotResourceModel, el models.ItemSnapshotResourceModel) bool {
for _, elOld := range els {
if elOld.GUID == el.GUID && elOld.Rollback != el.Rollback && el.Rollback.ValueBool() {
return true
}
}
return false
}
// restoreBservice performs BService Restore request.
// Returns error in case of failures.
func restoreBservice(ctx context.Context, serviceID uint64, c *client.Client) diag.Diagnostics {
diags := diag.Diagnostics{}
restoreReq := bservice.RestoreRequest{
ServiceID: serviceID,
}
tflog.Info(ctx, "restoreBservice: before calling CloudAPI().BService().Restore", map[string]any{"service_id": serviceID, "req": restoreReq})
res, err := c.CloudAPI().BService().Restore(ctx, restoreReq)
if err != nil {
diags.AddError(
"restoreBservice: cannot restore BService",
err.Error(),
)
return diags
}
tflog.Info(ctx, "restoreBservice: response from CloudAPI().BService().Restore", map[string]any{"service_id": serviceID, "response": res})
return nil
}