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/account/utilities/utility_resource_account.go

393 lines
14 KiB

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(),
RecursiveDelete: user.RecursiveDelete.ValueBool(), // default false
}
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
}