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.
393 lines
14 KiB
393 lines
14 KiB
7 months ago
|
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
|
||
|
}
|