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
}