package utilities
import (
"context"
"fmt"
"strconv"
"time"
"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"
decort "repository.basistech.ru/BASIS/decort-golang-sdk"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi/account"
"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/service/cloudapi/account/models"
"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/status"
)
// AccountResourceCheckPresence checks if account with accountId exists
func AccountResourceCheckPresence ( ctx context . Context , accountId uint64 , c * decort . DecortClient ) ( * account . RecordAccount , error ) {
tflog . Info ( ctx , fmt . Sprintf ( "AccountResourceCheckPresence: Get info about resource with ID - %v" , accountId ) )
accountRecord , err := c . CloudAPI ( ) . Account ( ) . Get ( ctx , account . GetRequest { AccountID : accountId } )
if err != nil {
return nil , fmt . Errorf ( "AccountResourceCheckPresence: cannot get info about resource with error: %w" , err )
}
tflog . Info ( ctx , "AccountResourceCheckPresence: response from CloudAPI().Account().Get" , map [ string ] any { "account_id" : accountId , "response" : accountRecord } )
return accountRecord , err
}
// AccountReadStatus loads account resource by its id, gets it current status. Performs restore and enable if needed for
// Deleted status.
// In case of failure returns errors.
func AccountReadStatus ( ctx context . Context , state * models . ResourceAccountModel , c * decort . DecortClient ) diag . Diagnostics {
tflog . Info ( ctx , "AccountReadStatus: Read status resource with ID" , map [ string ] any { "account_id" : state . Id . ValueString ( ) } )
diags := diag . Diagnostics { }
accountId , err := strconv . ParseUint ( state . Id . ValueString ( ) , 10 , 64 )
if err != nil {
diags . AddError ( "AccountReadStatus: Cannot parse resource ID from state" , err . Error ( ) )
return diags
}
recordAccount , err := AccountResourceCheckPresence ( ctx , accountId , c )
if err != nil {
diags . AddError ( "AccountReadStatus: Unable to Read account before status check" , err . Error ( ) )
return diags
}
// check resource status
switch recordAccount . Status {
case status . Disabled :
tflog . Info ( ctx , "The account is in status Disabled, troubles may occur with update. Please, enable account first." )
case status . Deleted :
restore := state . Restore . ValueBool ( )
if state . Restore . IsNull ( ) {
restore = true
} // default true
if restore {
// attempt to restore account
tflog . Info ( ctx , "AccountReadStatus: account with status.Deleted is being read, attempt to restore it" , map [ string ] any {
"account_id" : accountId ,
"status" : recordAccount . Status } )
diags . Append ( RestoreAccount ( ctx , accountId , c ) ... )
if diags . HasError ( ) {
tflog . Error ( ctx , "AccountReadStatus: cannot restore account" )
return diags
}
tflog . Info ( ctx , "AccountReadStatus: account restored successfully" , map [ string ] any { "account_id" : accountId } )
state . LastUpdated = types . StringValue ( time . Now ( ) . Format ( time . RFC850 ) )
} else {
tflog . Info ( ctx , "AccountReadStatus: account is i status Deleted but restore is not specified" )
}
case status . Destroyed :
diags . AddError (
"AccountReadStatus: Account is in status Destroyed" ,
fmt . Sprintf ( "the resource with account_id %d cannot be read or updated because it has been destroyed" , accountId ) ,
)
return diags
case status . Destroying :
diags . AddError (
"AccountReadStatus: Account is in progress with status Destroying" ,
fmt . Sprintf ( "the resource with account_id %d cannot be read or updated because it is currently being destroyed" , accountId ) ,
)
return diags
}
return nil
}
// RestoreAccount performs account Restore request.
// Returns error in case of failures.
func RestoreAccount ( ctx context . Context , accountId uint64 , c * decort . DecortClient ) diag . Diagnostics {
diags := diag . Diagnostics { }
restoreReq := account . RestoreRequest {
AccountID : accountId ,
}
tflog . Info ( ctx , "RestoreAccount: before calling CloudAPI().Account().Restore" , map [ string ] any { "account_id" : accountId , "req" : restoreReq } )
res , err := c . CloudAPI ( ) . Account ( ) . Restore ( ctx , restoreReq )
if err != nil {
diags . AddError (
"RestoreAccount: cannot restore account" ,
err . Error ( ) ,
)
return diags
}
tflog . Info ( ctx , "RestoreAccount: response from CloudAPI().Account().Restore" , map [ string ] any { "account_id" : accountId , "response" : res } )
return nil
}
// EnableDisableAccount performs account Enable/Disable request.
// Returns error in case of failures.
func EnableDisableAccount ( ctx context . Context , accountId uint64 , enable bool , c * decort . DecortClient ) diag . Diagnostics {
tflog . Info ( ctx , "Start EnableDisableAccount" , map [ string ] any { "account_id" : accountId } )
diags := diag . Diagnostics { }
if enable {
tflog . Info ( ctx , "EnableDisableAccount: before calling CloudAPI().Account().Enable" , map [ string ] any { "account_id" : accountId } )
res , err := c . CloudAPI ( ) . Account ( ) . Enable ( ctx , account . DisableEnableRequest { AccountID : accountId } )
if err != nil {
diags . AddError (
"EnableDisableAccount: cannot enable account" ,
err . Error ( ) ,
)
return diags
}
tflog . Info ( ctx , "EnableDisableAccount: response from CloudAPI().Account().Enable" , map [ string ] any { "account_id" : accountId , "response" : res } )
return nil
}
tflog . Info ( ctx , "EnableDisableAccount: before calling CloudAPI().Account().Disable" , map [ string ] any { "account_id" : accountId } )
res , err := c . CloudAPI ( ) . Account ( ) . Disable ( ctx , account . DisableEnableRequest { AccountID : accountId } )
if err != nil {
diags . AddError (
"EnableDisableAccount: cannot disable account" ,
err . Error ( ) ,
)
return diags
}
tflog . Info ( ctx , "EnableDisableAccount: response from CloudAPI().Account().Disable" , map [ string ] any { "account_id" : accountId , "response" : res } )
return nil
}
// UpdateAccount updates disk data: account_name, resource_limits, send_access_emails.
// Returns error in case of failures.
func UpdateAccount ( ctx context . Context , accountId uint64 , plan , state * models . ResourceAccountModel , c * decort . DecortClient ) diag . Diagnostics {
tflog . Info ( ctx , "Start UpdateAccount" , map [ string ] any { "account_id" : accountId } )
var diags diag . Diagnostics
var updateNeeded bool
updateReq := account . UpdateRequest {
AccountID : accountId ,
}
// check if account_name was changed
if ! plan . AccountName . Equal ( state . AccountName ) {
updateReq . Name = plan . AccountName . ValueString ( )
updateNeeded = true
}
// check if resource_limits were changed
if ! plan . ResourceLimits . Equal ( state . ResourceLimits ) && ! plan . ResourceLimits . IsUnknown ( ) {
tflog . Info ( ctx , "UpdateAccount: new ResourceLimits specified" , map [ string ] any { "account_id" : accountId } )
var resourceLimitsPlan models . ResourceLimitsInAccountResourceModel
diags . Append ( plan . ResourceLimits . As ( ctx , & resourceLimitsPlan , basetypes . ObjectAsOptions { } ) ... )
if diags . HasError ( ) {
tflog . Error ( ctx , "UpdateAccount: cannot populate ResourceLimits with plan.ResourceLimits object element" )
return diags
}
if resourceLimitsPlan . CUM . ValueFloat64 ( ) == 0 {
updateReq . MaxMemoryCapacity = - 1
} else {
updateReq . MaxMemoryCapacity = int64 ( resourceLimitsPlan . CUM . ValueFloat64 ( ) )
}
if resourceLimitsPlan . CUD . ValueFloat64 ( ) == 0 {
updateReq . MaxVDiskCapacity = - 1
} else {
updateReq . MaxVDiskCapacity = int64 ( resourceLimitsPlan . CUD . ValueFloat64 ( ) )
}
if resourceLimitsPlan . CUC . ValueFloat64 ( ) == 0 {
updateReq . MaxCPUCapacity = - 1
} else {
updateReq . MaxCPUCapacity = int64 ( resourceLimitsPlan . CUC . ValueFloat64 ( ) )
}
if resourceLimitsPlan . CUI . ValueFloat64 ( ) == 0 {
updateReq . MaxNumPublicIP = - 1
} else {
updateReq . MaxNumPublicIP = int64 ( resourceLimitsPlan . CUI . ValueFloat64 ( ) )
}
if resourceLimitsPlan . CUNP . ValueFloat64 ( ) == 0 {
updateReq . MaxNetworkPeerTransfer = - 1
} else {
updateReq . MaxNetworkPeerTransfer = int64 ( resourceLimitsPlan . CUNP . ValueFloat64 ( ) )
}
if resourceLimitsPlan . GPUUnits . ValueFloat64 ( ) == 0 {
updateReq . GPUUnits = - 1
} else {
updateReq . GPUUnits = int64 ( resourceLimitsPlan . GPUUnits . ValueFloat64 ( ) )
}
updateNeeded = true
}
// check if send_access_emails was changed
if ! plan . SendAccessEmails . Equal ( state . SendAccessEmails ) && ! plan . SendAccessEmails . IsNull ( ) {
updateReq . SendAccessEmails = plan . SendAccessEmails . ValueBool ( )
updateNeeded = true
}
if ! updateNeeded {
tflog . Info ( ctx , "UpdateAccount: no general account update is needed because neither account_name, nor resource_limits, nor send_access_emails were changed." , map [ string ] any {
"account_id" : plan . Id . ValueString ( ) ,
} )
return nil
}
// perform account update
tflog . Info ( ctx , "UpdateAccount: before calling CloudAPI().Account().Update" , map [ string ] any {
"account_id" : accountId ,
"req" : updateReq ,
} )
res , err := c . CloudAPI ( ) . Account ( ) . Update ( ctx , updateReq )
if err != nil {
diags . AddError ( "UpdateAccount: Unable to update account" ,
err . Error ( ) )
return diags
}
tflog . Info ( ctx , "UpdateAccount: response from CloudAPI().Account().Update" , map [ string ] any {
"account_id" : accountId ,
"response" : res } )
return nil
}
// AddDeleteUsersAccount adds/deletes users to/from account.
// In case of failure returns errors.
func AddDeleteUsersAccount ( ctx context . Context , accountId uint64 , plan , state * models . ResourceAccountModel , c * decort . DecortClient ) diag . Diagnostics {
tflog . Info ( ctx , "Start AddDeleteUsersAccount: new users specified" , map [ string ] any { "account_id" : accountId } )
diags := diag . Diagnostics { }
usersPlan := make ( [ ] models . UsersModel , 0 , len ( plan . Users . Elements ( ) ) )
diags . Append ( plan . Users . ElementsAs ( ctx , & usersPlan , true ) ... )
if diags . HasError ( ) {
tflog . Error ( ctx , "AddDeleteUsersAccount: cannot populate usersPlan with plan.Users list elements" )
return diags
}
usersState := make ( [ ] models . UsersModel , 0 , len ( state . Users . Elements ( ) ) )
diags . Append ( state . Users . ElementsAs ( ctx , & usersState , true ) ... )
if diags . HasError ( ) {
tflog . Error ( ctx , "AddDeleteUsersAccount: cannot populate usersState with state.Users list elements" )
return diags
}
// define users to be deleted, added and updated
var deletedUsers , addedUsers , updatedUsers [ ] models . UsersModel
for _ , user := range usersState {
if ! containsUser ( usersPlan , user ) {
deletedUsers = append ( deletedUsers , user )
}
}
for _ , user := range usersPlan {
if ! containsUser ( usersState , user ) {
addedUsers = append ( addedUsers , user )
} else if isChangedUser ( usersState , user ) {
updatedUsers = append ( updatedUsers , user )
}
}
// delete users
if len ( deletedUsers ) == 0 {
tflog . Info ( ctx , "AddDeleteUsersAccount: no users need to be deleted" , map [ string ] any { "account_id" : accountId } )
}
if len ( deletedUsers ) > 0 {
tflog . Info ( ctx , "AddDeleteUsersAccount: users need to be deleted" , map [ string ] any {
"accountId" : accountId ,
"deletedUsers" : deletedUsers } )
for _ , user := range deletedUsers {
delUserReq := account . DeleteUserRequest {
AccountID : accountId ,
UserID : user . UserID . ValueString ( ) ,
}
tflog . Info ( ctx , "AddDeleteUsersAccount: before calling CloudAPI().Account().DeleteUser" , map [ string ] any { "account_id" : accountId , "req" : delUserReq } )
res , err := c . CloudAPI ( ) . Account ( ) . DeleteUser ( ctx , delUserReq )
tflog . Info ( ctx , "AddDeleteUsersAccount: response from CloudAPI().Account().DeleteUser" , map [ string ] any { "account_id" : accountId , "response" : res } )
if err != nil {
diags . AddError (
"AddDeleteUsersAccount: can not delete user from account" ,
err . Error ( ) )
}
}
}
// add users
if len ( addedUsers ) == 0 {
tflog . Info ( ctx , "AddDeleteUsersAccount: no users needs to be added" , map [ string ] any { "account_id" : accountId } )
}
if len ( addedUsers ) > 0 {
tflog . Info ( ctx , "AddDeleteUsersAccount: users need to be added" , map [ string ] any { "account_id" : accountId } )
for _ , user := range addedUsers {
addUserReq := account . AddUserRequest {
AccountID : accountId ,
UserID : user . UserID . ValueString ( ) ,
AccessType : user . AccessType . ValueString ( ) ,
}
tflog . Info ( ctx , "AddDeleteUsersAccount: before calling CloudAPI().Account().AddUser" , map [ string ] any {
"account_id" : accountId ,
"addUserReq" : addUserReq } )
res , err := c . CloudAPI ( ) . Account ( ) . AddUser ( ctx , addUserReq )
if err != nil {
diags . AddError ( "AddDeleteUsersAccount: Unable to add users to account" ,
err . Error ( ) )
}
tflog . Info ( ctx , "AddDeleteUsersAccount: response from CloudAPI().Account().AddUser" , map [ string ] any {
"account_id" : accountId ,
"response" : res } )
}
}
// update users
if len ( updatedUsers ) == 0 {
tflog . Info ( ctx , "AddDeleteUsersAccount: no users needs to be updated" , map [ string ] any { "account_id" : accountId } )
}
if len ( updatedUsers ) > 0 {
tflog . Info ( ctx , "AddDeleteUsersAccount: users need to be updated" , map [ string ] any { "account_id" : accountId } )
for _ , user := range updatedUsers {
updUserReq := account . UpdateUserRequest {
AccountID : accountId ,
UserID : user . UserID . ValueString ( ) ,
AccessType : user . AccessType . ValueString ( ) ,
}
tflog . Info ( ctx , "AddDeleteUsersAccount: before calling CloudAPI().Account().UpdateUser" , map [ string ] any {
"account_id" : accountId ,
"updatedUsers" : updatedUsers } )
res , err := c . CloudAPI ( ) . Account ( ) . UpdateUser ( ctx , updUserReq )
if err != nil {
diags . AddError ( "AddDeleteUsersAccount: Unable to update users" ,
err . Error ( ) )
}
tflog . Info ( ctx , "AddDeleteUsersAccount: response from CloudAPI().Account().UpdateUser" , map [ string ] any {
"account_id" : accountId ,
"response" : res } )
}
}
return diags
}
func containsUser ( users [ ] models . UsersModel , target models . UsersModel ) bool {
for _ , user := range users {
if target . UserID == user . UserID {
return true
}
}
return false
}
func isChangedUser ( users [ ] models . UsersModel , target models . UsersModel ) bool {
for _ , user := range users {
if user . UserID . Equal ( target . UserID ) && ! user . AccessType . Equal ( target . AccessType ) {
return true
}
}
return false
}