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/cloudbroker/rg" "repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/service/cloudbroker/rg/models" "repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/status" ) func CreateRequestResourceRG(ctx context.Context, plan *models.ResourceRGModel) (rg.CreateRequest, diag.Diagnostics) { tflog.Info(ctx, "Start CreateRequestResourceRG", map[string]any{ "name": plan.Name.ValueString(), "account_id": plan.AccountID.ValueInt64(), "gid": plan.GID.ValueInt64(), }) // set up required parameters in resource group create request createReq := rg.CreateRequest{ Name: plan.Name.ValueString(), AccountID: uint64(plan.AccountID.ValueInt64()), GID: uint64(plan.GID.ValueInt64()), MaxCPUCapacity: -1, MaxVDiskCapacity: -1, MaxMemoryCapacity: -1, MaxNetworkPeerTransfer: -1, MaxNumPublicIP: -1, } // set up resource limits optional parameters if !plan.ResourceLimits.IsUnknown() { var resourceLimits models.ResourceLimitsModel diags := plan.ResourceLimits.As(ctx, &resourceLimits, basetypes.ObjectAsOptions{}) if diags.HasError() { tflog.Error(ctx, "CreateRequestResourceRG: cannot populate ResourceLimits with plan.ResourceLimits object element") return createReq, diags } if !resourceLimits.CUM.IsUnknown() { createReq.MaxMemoryCapacity = int64(resourceLimits.CUM.ValueFloat64()) } if !resourceLimits.CUDM.IsUnknown() { createReq.MaxVDiskCapacity = int64(resourceLimits.CUDM.ValueFloat64()) } if !resourceLimits.CUC.IsUnknown() { createReq.MaxCPUCapacity = int64(resourceLimits.CUC.ValueFloat64()) } if !resourceLimits.CUI.IsUnknown() { createReq.MaxNumPublicIP = int64(resourceLimits.CUI.ValueFloat64()) } if !resourceLimits.CUNP.IsUnknown() { createReq.MaxNetworkPeerTransfer = int64(resourceLimits.CUNP.ValueFloat64()) } } // set up defNet, owner, ipcidr, description, extNetId, extIp, registerComputes, uniqPools optional parameters if !plan.Owner.IsNull() { createReq.Owner = plan.Owner.ValueString() } if plan.DefNetType.IsNull() { createReq.DefNet = "PRIVATE" // default value } else { createReq.DefNet = plan.DefNetType.ValueString() } if !plan.IPCIDR.IsNull() { //createReq.IPCIDR = plan.IPCIDR.ValueString() } if !plan.Description.IsNull() { createReq.Description = plan.Description.ValueString() } if plan.ExtNetID.IsNull() { createReq.ExtNetID = 0 // default value 0 } else { createReq.ExtNetID = uint64(plan.ExtNetID.ValueInt64()) } if !plan.ExtIP.IsNull() { createReq.ExtIP = plan.ExtIP.ValueString() } if plan.RegisterComputes.IsNull() { createReq.RegisterComputes = false // default value } else { createReq.RegisterComputes = plan.RegisterComputes.ValueBool() } if !plan.UniqPools.IsUnknown() { uniqPools := make([]string, 0, len(plan.UniqPools.Elements())) diags := plan.UniqPools.ElementsAs(ctx, &uniqPools, true) if diags.HasError() { tflog.Error(ctx, "CreateRequestResourceRG: cannot populate UniqPools with plan.UniqPools object element") return createReq, diags } createReq.UniqPools = uniqPools } return createReq, nil } // RestoreRG performs resource group Restore request. Returns error in case of failures. func RestoreRG(ctx context.Context, rgId uint64, c *decort.DecortClient) diag.Diagnostics { diags := diag.Diagnostics{} restoreReq := rg.RestoreRequest{RGID: rgId} tflog.Info(ctx, "utilityRestoreRG: before calling CloudBroker().RG().Restore", map[string]any{"rgId": rgId, "req": restoreReq}) res, err := c.CloudBroker().RG().Restore(ctx, restoreReq) if err != nil { diags.AddError( "RestoreRG: cannot restore resource group", err.Error(), ) return diags } tflog.Info(ctx, "utilityRestoreRG: response from CloudBroker().RG().Restore", map[string]any{"rg_id": rgId, "response": res}) return nil } // EnableRG performs resource group Enable request func EnableRG(ctx context.Context, rgId uint64, plan *models.ResourceRGModel, c *decort.DecortClient) error { enableReq := rg.EnableRequest{RGID: rgId} tflog.Info(ctx, "utilityEnableRG: before calling CloudBroker().RG().Enable", map[string]any{"rg_id": rgId, "req": enableReq}) res, err := c.CloudBroker().RG().Enable(ctx, enableReq) tflog.Info(ctx, "utilityEnableRG: response from CloudBroker().RG().Enable", map[string]any{"rg_id": rgId, "response": res}) return err } // DisableRG performs resource group Disable request func DisableRG(ctx context.Context, rgId uint64, plan *models.ResourceRGModel, c *decort.DecortClient) error { disableReq := rg.DisableRequest{RGID: rgId} tflog.Info(ctx, "utilityDisableRG: before calling CloudBroker().RG().Disable", map[string]any{"rg_id": rgId, "req": disableReq}) res, err := c.CloudBroker().RG().Disable(ctx, disableReq) tflog.Info(ctx, "utilityDisableRG: response from CloudBroker().RG().Disable", map[string]any{"rg_id": rgId, "response": res}) return err } // UpdateRG compares plan and state for resource group fields name, description, quota, register_computes. // If any changes are detected, Update request is performed. If not, no update is performed. func UpdateRG(ctx context.Context, rgId uint64, plan, state *models.ResourceRGModel, c *decort.DecortClient) diag.Diagnostics { var updateNeeded bool var diags diag.Diagnostics updateReq := rg.UpdateRequest{ RGID: rgId, } if !plan.Name.Equal(state.Name) { updateReq.Name = plan.Name.ValueString() tflog.Info(ctx, "utilityUpdateRG: new name specified", map[string]any{ "rg_id": plan.Id.ValueString(), "name_plan": plan.Name.ValueString(), "name_state": state.Name.ValueString()}) updateNeeded = true } if !plan.Description.Equal(state.Description) { updateReq.Description = plan.Description.ValueString() tflog.Info(ctx, "utilityUpdateRG: new description specified", map[string]any{ "rg_id": plan.Id.ValueString(), "description_plan": plan.Description.ValueString(), "description_state": state.Description.ValueString()}) updateNeeded = true } if !plan.RegisterComputes.Equal(state.RegisterComputes) { if plan.RegisterComputes.IsNull() { updateReq.RegisterComputes = false // default value } else { updateReq.RegisterComputes = plan.RegisterComputes.ValueBool() } tflog.Info(ctx, "utilityUpdateRG: new register_computes specified", map[string]any{ "rg_id": plan.Id.ValueString(), "register_computes_plan": plan.RegisterComputes.ValueBool(), "register_computes_state": state.RegisterComputes.ValueBool()}) updateNeeded = true } var updResLimitsNeeded bool var resLimitsPlan, resLimitsState models.ResourceLimitsModel if !plan.ResourceLimits.IsUnknown() { diags = plan.ResourceLimits.As(ctx, &resLimitsPlan, basetypes.ObjectAsOptions{}) if diags.HasError() { return diags } } if !state.ResourceLimits.IsNull() { diags = state.ResourceLimits.As(ctx, &resLimitsState, basetypes.ObjectAsOptions{}) if diags.HasError() { return diags } } if !plan.UniqPools.IsUnknown() { if !plan.UniqPools.Equal(state.UniqPools) { uniqPools := make([]string, 0, len(plan.UniqPools.Elements())) diags := plan.UniqPools.ElementsAs(ctx, &uniqPools, true) if diags.HasError() { tflog.Error(ctx, "UpdateResourceRG: cannot populate UniqPools with plan.UniqPools object element") return diags } updateReq.UniqPools = uniqPools } } if !plan.ResourceLimits.IsUnknown() { if !resLimitsPlan.CUM.Equal(resLimitsState.CUM) { updateReq.MaxMemoryCapacity = int64(resLimitsPlan.CUM.ValueFloat64()) updResLimitsNeeded = true } if !resLimitsPlan.CUDM.Equal(resLimitsState.CUDM) { updateReq.MaxVDiskCapacity = int64(resLimitsPlan.CUDM.ValueFloat64()) updResLimitsNeeded = true } if !resLimitsPlan.CUC.Equal(resLimitsState.CUC) { updateReq.MaxCPUCapacity = int64(resLimitsPlan.CUC.ValueFloat64()) updResLimitsNeeded = true } if !resLimitsPlan.CUNP.Equal(resLimitsState.CUNP) { updateReq.MaxNetworkPeerTransfer = int64(resLimitsPlan.CUNP.ValueFloat64()) updResLimitsNeeded = true } if !resLimitsPlan.CUI.Equal(resLimitsState.CUI) { updateReq.MaxNumPublicIP = int64(resLimitsPlan.CUI.ValueFloat64()) updResLimitsNeeded = true } } if updResLimitsNeeded { tflog.Info(ctx, "utilityUpdateRG: new resource limits specified", map[string]any{ "rg_id": plan.Id.ValueString()}) updateNeeded = true } if updateNeeded { tflog.Info(ctx, "utilityUpdateRG: before calling CloudBroker().RG().Update", map[string]any{"rg_id": plan.Id.ValueString(), "req": updateReq}) res, err := c.CloudBroker().RG().Update(ctx, updateReq) tflog.Info(ctx, "utilityUpdateRG: response from CloudBroker().RG().Update", map[string]any{"rg_id": plan.Id.ValueString(), "response": res}) if err != nil { diags.AddError("can not update RG", err.Error()) return diags } } if !updateNeeded { tflog.Info(ctx, "utilityUpdateRG: call for CloudBroker().RG().Update was not needed", map[string]any{"rg_id": plan.Id.ValueString()}) } return nil } // EnableDisableCreateRG performs Enable request is enable is true, and Disable request otherwise. // In case of failure returns warnings. func EnableDisableCreateRG(ctx context.Context, rgId uint64, plan *models.ResourceRGModel, c *decort.DecortClient) diag.Diagnostics { diags := diag.Diagnostics{} var enable bool if plan.Enable.IsNull() { enable = true // default value } else { enable = plan.Enable.ValueBool() } tflog.Info(ctx, "EnableDisableCreateRG: resource group to be enabled/disabled", map[string]any{ "rg_id": rgId, "enable": enable}) if enable { err := EnableRG(ctx, rgId, plan, c) if err != nil { diags.AddWarning( "EnableDisableCreateRG: cannot enable rg", err.Error(), ) return diags } } if !enable { err := DisableRG(ctx, rgId, plan, c) if err != nil { diags.AddWarning( "EnableDisableCreateRG: cannot disable rg", err.Error(), ) return diags } } tflog.Info(ctx, "EnableDisableCreateRG: resource group is successfully enabled/disabled", map[string]any{"rg_id": rgId, "enable": enable}) return nil } // EnableDisableUpdateRG performs Enable request is enable is true, and Disable request otherwise. // In case of failure returns errors. func EnableDisableUpdateRG(ctx context.Context, rgId uint64, plan *models.ResourceRGModel, c *decort.DecortClient) diag.Diagnostics { diags := diag.Diagnostics{} var enable bool if plan.Enable.IsNull() { enable = true // default value } else { enable = plan.Enable.ValueBool() } tflog.Info(ctx, "EnableDisableUpdateRG: resource group to be enabled/disabled", map[string]any{ "rg_id": rgId, "enable": enable}) if enable { err := EnableRG(ctx, rgId, plan, c) if err != nil { diags.AddError( "EnableDisableUpdateRG: cannot enable rg", err.Error(), ) return diags } } if !enable { err := DisableRG(ctx, rgId, plan, c) if err != nil { diags.AddError( "EnableDisableUpdateRG: cannot disable rg", err.Error(), ) return diags } } tflog.Info(ctx, "EnableDisableUpdateRG: resource group is successfully enabled/disabled", map[string]any{"rg_id": rgId, "enable": enable}) return nil } // AccessUpdateRG compares plan and state for resource group field access. // If changes are detected, AccessRevoke request is performed for each deleted access user and AccessGrant request is // performed for each added access user. If no changes are detected, no requests performed. // Returns errors in case of failures. func AccessUpdateRG(ctx context.Context, rgId uint64, plan, state *models.ResourceRGModel, c *decort.DecortClient) diag.Diagnostics { diags := diag.Diagnostics{} itemsAccessPlan := make([]models.AccessModel, 0, len(plan.Access.Elements())) diags = plan.Access.ElementsAs(ctx, &itemsAccessPlan, false) if diags.HasError() { tflog.Error(ctx, "AccessUpdateRG: cannot populate itemsAccess with plan.Access List elements") return diags } itemsAccessState := make([]models.AccessModel, 0, len(state.Access.Elements())) diags = state.Access.ElementsAs(ctx, &itemsAccessState, false) if diags.HasError() { tflog.Error(ctx, "AccessUpdateRG: cannot populate itemsAccess with state.Access List elements") return diags } // define accesses to be revoked and revoke them var deletedAccess []models.AccessModel for _, accessStateElem := range itemsAccessState { if !accessStateElem.Contains(itemsAccessPlan) { deletedAccess = append(deletedAccess, accessStateElem) } } if len(deletedAccess) == 0 { tflog.Info(ctx, "AccessUpdateRG: no access needs to be revoked", map[string]any{ "rg_id": plan.Id.ValueString()}) } if len(deletedAccess) > 0 { tflog.Info(ctx, "AccessUpdateRG: access needs to be revoked", map[string]any{ "rg_id": plan.Id.ValueString(), "deleted_access": deletedAccess}) for _, deletedAccessItem := range deletedAccess { revokeReq := rg.AccessRevokeRequest{ RGID: rgId, User: deletedAccessItem.User.ValueString(), } tflog.Info(ctx, "AccessUpdateRG: before calling CloudBroker().RG().AccessRevoke", map[string]any{"rg_id": plan.Id.ValueString(), "req": revokeReq}) res, err := c.CloudBroker().RG().AccessRevoke(ctx, revokeReq) tflog.Info(ctx, "AccessUpdateRG: response from CloudBroker().RG().AccessRevoke", map[string]any{"rg_id": plan.Id.ValueString(), "response": res}) if err != nil { diags.AddError( "AccessUpdateRG: cannot revoke access for rg", err.Error()) } } } // define accesses to be granted and grant them var addedAccess []models.AccessModel for _, accessPlanElem := range itemsAccessPlan { if !accessPlanElem.Contains(itemsAccessState) { addedAccess = append(addedAccess, accessPlanElem) } } if len(addedAccess) == 0 { tflog.Info(ctx, "AccessUpdateRG: no access need to be granted", map[string]any{ "rg_id": plan.Id.ValueString()}) } if len(addedAccess) > 0 { tflog.Info(ctx, "AccessUpdateRG: access needs to be granted", map[string]any{ "rg_id": plan.Id.ValueString(), "added_access": addedAccess}) for _, addedAccessItem := range addedAccess { grantReq := rg.AccessGrantRequest{ RGID: rgId, User: addedAccessItem.User.ValueString(), Right: addedAccessItem.Right.ValueString(), } tflog.Info(ctx, "AccessUpdateRG: before calling CloudBroker().RG().AccessGrant", map[string]any{"rg_id": plan.Id.ValueString(), "req": grantReq}) res, err := c.CloudBroker().RG().AccessGrant(ctx, grantReq) tflog.Info(ctx, "AccessUpdateRG: response from CloudBroker().RG().AccessGrant", map[string]any{"rg_id": plan.Id.ValueString(), "response": res}) if err != nil { diags.AddError( "AccessUpdateRG: cannot grant access for rg", err.Error()) } } } return diags } // AccessCreateRG grants access to users specified in access field for created resource. // In case of failure returns warnings. func AccessCreateRG(ctx context.Context, rgId uint64, plan *models.ResourceRGModel, c *decort.DecortClient) diag.Diagnostics { diags := diag.Diagnostics{} if len(plan.Access.Elements()) != 0 { tflog.Info(ctx, "AccessCreateRG: access needs to be granted", map[string]any{ "rg_id": rgId, "access_plan": plan.Access.Elements()}) itemsAccessPlan := make([]models.AccessModel, 0, len(plan.Access.Elements())) diagsItem := plan.Access.ElementsAs(ctx, &itemsAccessPlan, false) if diagsItem.HasError() { tflog.Warn(ctx, "cannot populate itemsAccess with plan.Access List elements") diags.AddWarning(fmt.Sprintf("AccessCreateRG: Unable to get access info for RG %d", rgId), "cannot populate itemsAccess with plan.Access List elements", ) return diags } for _, addedAccessItem := range itemsAccessPlan { grantReq := rg.AccessGrantRequest{ RGID: rgId, User: addedAccessItem.User.ValueString(), Right: addedAccessItem.Right.ValueString(), } tflog.Info(ctx, "AccessCreateRG: before calling CloudBroker().RG().AccessGrant", map[string]any{ "rg_id": rgId, "req": grantReq}) res, err := c.CloudBroker().RG().AccessGrant(ctx, grantReq) if err != nil { diags.AddWarning("AccessCreateRG: Unable to grant access for RG", err.Error()) } tflog.Info(ctx, "AccessCreateRG: response from CloudBroker().RG().AccessGrant", map[string]any{ "rg_id": rgId, "response": res}) } } if len(plan.Access.Elements()) == 0 { tflog.Info(ctx, "AccessCreateRG: no access need to be granted", map[string]any{ "rg_id": rgId, "access_plan": plan.Access.Elements()}) } return diags } // SetDefNetUpdateRG compares plan and state for resource group update field def_net. // If any changes are detected, SetDefNet request is performed. If not, no SetDefNet is performed. // Returns error in case of failures. func SetDefNetUpdateRG(ctx context.Context, rgId uint64, plan, state *models.ResourceRGModel, c *decort.DecortClient) diag.Diagnostics { diags := diag.Diagnostics{} var setDefNetNeeded bool setDefNetReq := rg.SetDefNetRequest{ RGID: rgId, } var itemDefNetPlan, itemDefNetState models.DefNetModel if !plan.DefNet.IsNull() { diags.Append(plan.DefNet.As(ctx, &itemDefNetPlan, basetypes.ObjectAsOptions{})...) if diags.HasError() { tflog.Error(ctx, "SetDefNetUpdateRG: cannot populate defNet with plan.DefNet object element") return diags } } if !state.DefNet.IsNull() { diags.Append(state.DefNet.As(ctx, &itemDefNetState, basetypes.ObjectAsOptions{})...) if diags.HasError() { tflog.Error(ctx, "SetDefNetUpdateRG: cannot populate defNet with state.DefNet object element") return diags } } if !plan.DefNet.IsNull() && !state.DefNet.IsNull() { if !itemDefNetPlan.NetId.Equal(itemDefNetState.NetId) { setDefNetNeeded = true } if !itemDefNetPlan.NetType.Equal(itemDefNetState.NetType) { setDefNetNeeded = true } } else if !plan.DefNet.IsNull() { setDefNetNeeded = true } if setDefNetNeeded { tflog.Info(ctx, "utilitySetDefNetUpdateRG: new def_net specified", map[string]any{ "rg_id": plan.Id.ValueString(), "def_net_plan": plan.DefNet, "def_net_state": state.DefNet}) setDefNetReq.NetType = itemDefNetPlan.NetType.ValueString() if itemDefNetPlan.NetId.IsNull() { setDefNetReq.NetID = 0 // default value } else { setDefNetReq.NetID = uint64(itemDefNetPlan.NetId.ValueInt64()) } tflog.Info(ctx, "utilitySetDefNetUpdateRG: before calling CloudBroker().RG().SetDefNet", map[string]any{"rg_id": plan.Id.ValueString(), "req": setDefNetReq}) res, err := c.CloudBroker().RG().SetDefNet(ctx, setDefNetReq) if err != nil { diags.AddError( "SetDefNetUpdateRG: can not set defNet for rg", err.Error()) return diags } tflog.Info(ctx, "utilitySetDefNetUpdateRG: response from CloudBroker().RG().SetDefNet", map[string]any{"rg_id": plan.Id.ValueString(), "response": res}) } if !setDefNetNeeded { tflog.Info(ctx, "utilitySetDefNetUpdateRG: call for CloudBroker().RG().SetDefNet was not needed", map[string]any{ "rg_id": plan.Id.ValueString(), "def_net_plan": plan.DefNet, "def_net_state": state.DefNet}) } return nil } // SetDefNetCreateRG performs SetDefNet request if def_net field is not empty. Otherwise, no SetDefNet request is performed. // In case of failure returns warnings. func SetDefNetCreateRG(ctx context.Context, rgId uint64, plan *models.ResourceRGModel, c *decort.DecortClient) diag.Diagnostics { diags := diag.Diagnostics{} setDefNetReq := rg.SetDefNetRequest{RGID: rgId} var itemDefNetPlan models.DefNetModel if !plan.DefNet.IsNull() { tflog.Info(ctx, "SetDefNetCreateRG: new def_net specified", map[string]any{ "rg_id": rgId, "def_net_plan": plan.DefNet}) diagItem := plan.DefNet.As(ctx, &itemDefNetPlan, basetypes.ObjectAsOptions{}) if diagItem.HasError() { diags.AddWarning( fmt.Sprintf("SetDefNetCreateRG: Unable to setDefNet for RG %d", rgId), "cannot populate defNet with plan.DefNet object element for rg", ) return diags } setDefNetReq.NetType = itemDefNetPlan.NetType.ValueString() if itemDefNetPlan.NetId.IsNull() { setDefNetReq.NetID = 0 // default value } else { setDefNetReq.NetID = uint64(itemDefNetPlan.NetId.ValueInt64()) } tflog.Info(ctx, "SetDefNetCreateRG: before calling CloudBroker().RG().SetDefNet", map[string]any{"rg_id": rgId, "req": setDefNetReq}) res, err := c.CloudBroker().RG().SetDefNet(ctx, setDefNetReq) tflog.Info(ctx, "SetDefNetCreateRG: response from CloudBroker().RG().SetDefNet", map[string]any{"rg_id": rgId, "response": res}) if err != nil { diags.AddWarning( "SetDefNetCreateRG: Unable to setDefNet for RG", err.Error(), ) return diags } } if plan.DefNet.IsNull() { tflog.Info(ctx, "SetDefNetCreateRG: call for CloudBroker().RG().SetDefNet was not needed", map[string]any{ "rg_id": rgId, "def_net_plan": plan.DefNet}) } return nil } // UpdateComputeFeature performs СomputeFeatureUpdate request if compute_feature field is not empty and need update. // In case of failure returns error. func UpdateComputeFeature(ctx context.Context, rgId uint64, plan *models.ResourceRGModel, c *decort.DecortClient) diag.Diagnostics { diags := diag.Diagnostics{} req := rg.UpdateComputeFeaturesRequest{RGID: rgId} computeFeatures := make([]string, 0, len(plan.ComputeFeatures.Elements())) diags = plan.ComputeFeatures.ElementsAs(ctx, &computeFeatures, true) if diags.HasError() { tflog.Error(ctx, "UpdateComputeFeature: cannot populate ComputeFeature with plan.ComputeFeatures object element") return diags } req.ComputeFeatures = computeFeatures tflog.Info(ctx, "UpdateComputeFeature: before call CloudBroker().LB().UpdateComputeFeatures", map[string]any{"req": req}) _, err := c.CloudBroker().RG().UpdateComputeFeatures(ctx, req) if err != nil { diags.AddError("UpdateComputeFeature: unable to update compute features", err.Error()) return diags } tflog.Info(ctx, "UpdateComputeFeature: compute features updated") return nil } // UpdateCpuAllocationParameter performs setCpuAllocationParameter request if cpu_allocation_parameter field is not empty and need update. // In case of failure returns error. func UpdateCpuAllocationParameter(ctx context.Context, rgId uint64, plan *models.ResourceRGModel, c *decort.DecortClient) diag.Diagnostics { diags := diag.Diagnostics{} req := rg.SetCPUAllocationParameterRequest{ RGID: rgId, StrictLoose: plan.CPUAllocationParameter.ValueString(), } tflog.Info(ctx, "UpdateCpuAllocationParameter: before call CloudBroker().LB().SetCPUAllocationParameter", map[string]any{"req": req}) _, err := c.CloudBroker().RG().SetCPUAllocationParameter(ctx, req) if err != nil { diags.AddError("UpdateCpuAllocationParameter: unable to update cpu allocation parameter", err.Error()) return diags } tflog.Info(ctx, "UpdateCpuAllocationParameter: cpu allocation parameter updated") return nil } // UpdateCpuAllocationRatio performs setCpuAllocationRatio request if ratio field is not empty and need update. // In case of failure returns error. func UpdateCpuAllocationRatio(ctx context.Context, rgId uint64, plan *models.ResourceRGModel, c *decort.DecortClient) diag.Diagnostics { diags := diag.Diagnostics{} req := rg.SetCPUAllocationRatioRequest{ RGID: rgId, Ratio: plan.CPUAllocationRatio.ValueFloat64(), } tflog.Info(ctx, "UpdateCpuAllocationRatio: before call CloudBroker().LB().SetCPUAllocationRatio", map[string]any{"req": req}) _, err := c.CloudBroker().RG().SetCPUAllocationRatio(ctx, req) if err != nil { diags.AddError("UpdateCpuAllocationRatio: unable to update cpu allocation ratio", err.Error()) return diags } tflog.Info(ctx, "UpdateCpuAllocationRatio: cpu allocation ratio updated") return nil } // RGReadStatus loads rg resource by ids id, gets it current status. Performs restore and enable if needed for // Deleted status. // In case of failure returns errors. func RGReadStatus(ctx context.Context, state *models.ResourceRGModel, c *decort.DecortClient) diag.Diagnostics { tflog.Info(ctx, "RGReadStatus: Read status rg with ID", map[string]any{"rg_id": state.Id.ValueString()}) diags := diag.Diagnostics{} rgId, err := strconv.ParseUint(state.Id.ValueString(), 10, 64) if err != nil { diags.AddError("RGReadStatus: Cannot parse resource group ID from state", err.Error()) return diags } recordRG, err := RGCheckPresence(ctx, rgId, c) if err != nil { diags.AddError("RGReadStatus: Unable to Read RG before status check", err.Error()) return diags } tflog.Info(ctx, "RGReadStatus: resource group values before status check", map[string]any{ "rg_id": recordRG.ID, "updated_recordRG": recordRG}) // check resource status switch recordRG.Status { case status.Modeled: diags.AddError( "RG is in status Modeled", "please, contact support for more information", ) return diags case status.Deleted: tflog.Info(ctx, "RGReadStatus: resource group with status.Deleted is being checked", map[string]any{ "rg_id": recordRG.ID, "status": recordRG.Status}) // restore and enable resource group in case it is required if state.Restore.IsNull() || state.Restore.ValueBool() { // default true or user set-up true diags.Append(RestoreRG(ctx, rgId, c)...) if diags.HasError() { tflog.Error(ctx, "RGReadStatus: cannot restore rg") return diags } tflog.Info(ctx, "RGReadStatus: resource group restored successfully", map[string]any{"rg_id": recordRG.ID}) state.LastUpdated = types.StringValue(time.Now().Format(time.RFC850)) if state.Enable.IsNull() || state.Enable.ValueBool() { // default true or user set-up true err := EnableRG(ctx, rgId, state, c) if err != nil { diags.AddError( "RGReadStatus: Unable to Enable RG", err.Error(), ) return diags } tflog.Info(ctx, "RGReadStatus: resource group enabled successfully", map[string]any{"rg_id": recordRG.ID}) } } case status.Destroyed: diags.AddError( "RGReadStatus: RG is in status Destroyed", fmt.Sprintf("the resource with rg_id %d cannot be read because it has been destroyed", recordRG.ID), ) return diags } return nil }