1.1.0
This commit is contained in:
@@ -35,8 +35,8 @@ func RGDataSource(ctx context.Context, state *models.DataSourceRGModel, c *decor
|
||||
|
||||
id := uuid.New()
|
||||
*state = models.DataSourceRGModel{
|
||||
RGID: state.RGID,
|
||||
Reason: state.Reason,
|
||||
RGID: state.RGID,
|
||||
|
||||
Timeouts: state.Timeouts,
|
||||
|
||||
Id: types.StringValue(id.String()),
|
||||
|
||||
@@ -30,8 +30,8 @@ func RGUsageDataSource(ctx context.Context, state *models.DataSourceRGUsageModel
|
||||
|
||||
id := uuid.New()
|
||||
*state = models.DataSourceRGUsageModel{
|
||||
RGID: state.RGID,
|
||||
Reason: state.Reason,
|
||||
RGID: state.RGID,
|
||||
|
||||
Timeouts: state.Timeouts,
|
||||
Id: types.StringValue(id.String()),
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package flattens
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
decort "repository.basistech.ru/BASIS/decort-golang-sdk"
|
||||
"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/flattens"
|
||||
"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/service/cloudbroker/rg/models"
|
||||
"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/service/cloudbroker/rg/utilities"
|
||||
)
|
||||
|
||||
// RGResource flattens resource for rg (resource group).
|
||||
// Return error in case resource is not found on the platform.
|
||||
// Flatten errors are added to tflog.
|
||||
func RGResource(ctx context.Context, plan *models.ResourceRGModel, c *decort.DecortClient) diag.Diagnostics {
|
||||
tflog.Info(ctx, "Start flattens.RGResource")
|
||||
|
||||
diags := diag.Diagnostics{}
|
||||
|
||||
rgId, err := strconv.ParseUint(plan.Id.ValueString(), 10, 64)
|
||||
if err != nil {
|
||||
diags.AddError("Cannot parse resource group ID from state", err.Error())
|
||||
return diags
|
||||
}
|
||||
|
||||
recordRG, err := utilities.RGCheckPresence(ctx, rgId, c)
|
||||
if err != nil {
|
||||
diags.AddError(fmt.Sprintf("Cannot get info about resource group with ID %v", rgId), err.Error())
|
||||
return diags
|
||||
}
|
||||
|
||||
tflog.Info(ctx, "flattens.RGResource: before flatten", map[string]any{"rg_id": rgId, "recordRG": recordRG})
|
||||
|
||||
*plan = models.ResourceRGModel{
|
||||
AccountID: types.Int64Value(int64(recordRG.AccountID)),
|
||||
GID: types.Int64Value(int64(recordRG.GID)),
|
||||
Name: types.StringValue(recordRG.Name),
|
||||
|
||||
DefNetType: plan.DefNetType,
|
||||
IPCIDR: plan.IPCIDR,
|
||||
ResourceLimits: flattenResourceLimits(ctx, &recordRG.ResourceLimits),
|
||||
ComputeFeatures: plan.ComputeFeatures,
|
||||
ExtNetID: plan.ExtNetID,
|
||||
ExtIP: plan.ExtIP,
|
||||
Owner: plan.Owner,
|
||||
Access: plan.Access,
|
||||
DefNet: plan.DefNet,
|
||||
Description: plan.Description,
|
||||
Force: plan.Force,
|
||||
Permanently: plan.Permanently,
|
||||
RegisterComputes: plan.RegisterComputes,
|
||||
Restore: plan.Restore,
|
||||
Enable: plan.Enable,
|
||||
UniqPools: plan.UniqPools,
|
||||
Timeouts: plan.Timeouts,
|
||||
|
||||
RGID: types.Int64Value(int64(recordRG.ID)),
|
||||
LastUpdated: plan.LastUpdated,
|
||||
AccountName: types.StringValue(recordRG.AccountName),
|
||||
ACL: flattenACL(ctx, &recordRG.ACL),
|
||||
CPUAllocationParameter: types.StringValue(recordRG.CPUAllocationParameter),
|
||||
CPUAllocationRatio: types.Float64Value(recordRG.CPUAllocationRatio),
|
||||
DefNetID: types.Int64Value(recordRG.DefNetID),
|
||||
DeletedBy: types.StringValue(recordRG.DeletedBy),
|
||||
DeletedTime: types.Int64Value(int64(recordRG.DeletedTime)),
|
||||
GUID: types.Int64Value(int64(recordRG.GUID)),
|
||||
Id: types.StringValue(strconv.Itoa(int(recordRG.ID))),
|
||||
LockStatus: types.StringValue(recordRG.LockStatus),
|
||||
Milestones: types.Int64Value(int64(recordRG.Milestones)),
|
||||
Secret: types.StringValue(recordRG.Secret),
|
||||
Status: types.StringValue(recordRG.Status),
|
||||
UpdatedBy: types.StringValue(recordRG.UpdatedBy),
|
||||
UpdatedTime: types.Int64Value(int64(recordRG.UpdatedTime)),
|
||||
ResourceTypes: flattens.FlattenSimpleTypeToList(ctx, types.StringType, &recordRG.ResTypes),
|
||||
VINS: flattens.FlattenSimpleTypeToList(ctx, types.Int64Type, &recordRG.VINS),
|
||||
VMS: flattens.FlattenSimpleTypeToList(ctx, types.Int64Type, &recordRG.VMs),
|
||||
}
|
||||
|
||||
tflog.Info(ctx, "flattenResourceRG: after flatten", map[string]any{"rg_id": plan.Id.ValueString()})
|
||||
|
||||
tflog.Info(ctx, "End FlattenRGResource")
|
||||
return nil
|
||||
}
|
||||
41
internal/service/cloudbroker/rg/input_checks.go
Normal file
41
internal/service/cloudbroker/rg/input_checks.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package rg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
decort "repository.basistech.ru/BASIS/decort-golang-sdk"
|
||||
"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/service/cloudbroker/ic"
|
||||
"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/service/cloudbroker/rg/models"
|
||||
)
|
||||
|
||||
func resourceRgInputChecks(ctx context.Context, plan *models.ResourceRGModel, c *decort.DecortClient) diag.Diagnostics {
|
||||
diags := diag.Diagnostics{}
|
||||
|
||||
accountId := uint64(plan.AccountID.ValueInt64())
|
||||
tflog.Info(ctx, "resourceRgInputChecks: exist account check", map[string]any{"account_id": accountId})
|
||||
err := ic.ExistAccount(ctx, accountId, c)
|
||||
if err != nil {
|
||||
diags.AddError(fmt.Sprintf("Cannot get info about account with ID %v", accountId), err.Error())
|
||||
}
|
||||
|
||||
gid := uint64(plan.GID.ValueInt64())
|
||||
tflog.Info(ctx, "resourceRgInputChecks: exist gid check", map[string]any{"gid": gid})
|
||||
err = ic.ExistGID(ctx, gid, c)
|
||||
if err != nil {
|
||||
diags.AddError(fmt.Sprintf("Cannot get info about gid with ID %v", gid), err.Error())
|
||||
}
|
||||
|
||||
if !plan.ExtNetID.IsNull() {
|
||||
extnetId := uint64(plan.ExtNetID.ValueInt64())
|
||||
tflog.Info(ctx, "resourceRgInputChecks: exist ext_net check", map[string]any{"ext_net_id": extnetId})
|
||||
err = ic.ExistExtNetInRG(ctx, extnetId, accountId, c)
|
||||
if err != nil {
|
||||
diags.AddError(fmt.Sprintf("Cannot get info about ext net with ID %v", extnetId), err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return diags
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
type DataSourceRGModel struct {
|
||||
// request fields
|
||||
RGID types.Int64 `tfsdk:"rg_id"`
|
||||
Reason types.String `tfsdk:"reason"`
|
||||
Timeouts timeouts.Value `tfsdk:"timeouts"`
|
||||
|
||||
// response fields
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
type DataSourceRGUsageModel struct {
|
||||
// request fields
|
||||
RGID types.Int64 `tfsdk:"rg_id"`
|
||||
Reason types.String `tfsdk:"reason"`
|
||||
Timeouts timeouts.Value `tfsdk:"timeouts"`
|
||||
|
||||
// response fields
|
||||
|
||||
76
internal/service/cloudbroker/rg/models/model_resource_rg.go
Normal file
76
internal/service/cloudbroker/rg/models/model_resource_rg.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
type ResourceRGModel struct {
|
||||
// request fields - required
|
||||
AccountID types.Int64 `tfsdk:"account_id"`
|
||||
GID types.Int64 `tfsdk:"gid"`
|
||||
Name types.String `tfsdk:"rg_name"`
|
||||
|
||||
// request fields - optional
|
||||
DefNetType types.String `tfsdk:"def_net_type"`
|
||||
IPCIDR types.String `tfsdk:"ipcidr"`
|
||||
ExtNetID types.Int64 `tfsdk:"ext_net_id"`
|
||||
ExtIP types.String `tfsdk:"ext_ip"`
|
||||
Owner types.String `tfsdk:"owner"`
|
||||
ResourceLimits types.Object `tfsdk:"resource_limits"`
|
||||
CPUAllocationParameter types.String `tfsdk:"cpu_allocation_parameter"`
|
||||
CPUAllocationRatio types.Float64 `tfsdk:"cpu_allocation_ratio"`
|
||||
Access types.List `tfsdk:"access"`
|
||||
DefNet types.Object `tfsdk:"def_net"`
|
||||
Description types.String `tfsdk:"description"`
|
||||
Force types.Bool `tfsdk:"force"`
|
||||
Permanently types.Bool `tfsdk:"permanently"`
|
||||
RegisterComputes types.Bool `tfsdk:"register_computes"`
|
||||
Restore types.Bool `tfsdk:"restore"`
|
||||
Enable types.Bool `tfsdk:"enable"`
|
||||
Timeouts timeouts.Value `tfsdk:"timeouts"`
|
||||
|
||||
// response fields
|
||||
RGID types.Int64 `tfsdk:"rg_id"`
|
||||
LastUpdated types.String `tfsdk:"last_updated"`
|
||||
AccountName types.String `tfsdk:"account_name"`
|
||||
ACL types.List `tfsdk:"acl"`
|
||||
ComputeFeatures types.List `tfsdk:"compute_features"`
|
||||
CreatedBy types.String `tfsdk:"created_by"`
|
||||
CreatedTime types.Int64 `tfsdk:"created_time"`
|
||||
DefNetID types.Int64 `tfsdk:"def_net_id"`
|
||||
DeletedBy types.String `tfsdk:"deleted_by"`
|
||||
DeletedTime types.Int64 `tfsdk:"deleted_time"`
|
||||
GUID types.Int64 `tfsdk:"guid"`
|
||||
Id types.String `tfsdk:"id"`
|
||||
LockStatus types.String `tfsdk:"lock_status"`
|
||||
Milestones types.Int64 `tfsdk:"milestones"`
|
||||
Secret types.String `tfsdk:"secret"`
|
||||
Status types.String `tfsdk:"status"`
|
||||
UniqPools types.List `tfsdk:"uniq_pools"`
|
||||
UpdatedBy types.String `tfsdk:"updated_by"`
|
||||
UpdatedTime types.Int64 `tfsdk:"updated_time"`
|
||||
ResourceTypes types.List `tfsdk:"resource_types"`
|
||||
VINS types.List `tfsdk:"vins"`
|
||||
VMS types.List `tfsdk:"vms"`
|
||||
}
|
||||
|
||||
type AccessModel struct {
|
||||
User types.String `tfsdk:"user"`
|
||||
Right types.String `tfsdk:"right"`
|
||||
}
|
||||
|
||||
type DefNetModel struct {
|
||||
NetType types.String `tfsdk:"net_type"`
|
||||
NetId types.Int64 `tfsdk:"net_id"`
|
||||
}
|
||||
|
||||
// Contains returns true if accessList contains a as an element. Otherwise it returns false.
|
||||
func (a *AccessModel) Contains(accessList []AccessModel) bool {
|
||||
for _, accessElem := range accessList {
|
||||
if a.User.Equal(accessElem.User) && a.Right.Equal(accessElem.Right) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
457
internal/service/cloudbroker/rg/resource_rg.go
Normal file
457
internal/service/cloudbroker/rg/resource_rg.go
Normal file
@@ -0,0 +1,457 @@
|
||||
package rg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
|
||||
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
decort "repository.basistech.ru/BASIS/decort-golang-sdk"
|
||||
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi/rg"
|
||||
"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/client"
|
||||
"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/constants"
|
||||
"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/service/cloudbroker/rg/flattens"
|
||||
"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/service/cloudbroker/rg/models"
|
||||
"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/service/cloudbroker/rg/schemas"
|
||||
"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/service/cloudbroker/rg/utilities"
|
||||
)
|
||||
|
||||
// Ensure the implementation satisfies the expected interfaces.
|
||||
var (
|
||||
_ resource.Resource = &resourceRG{}
|
||||
_ resource.ResourceWithImportState = &resourceRG{}
|
||||
)
|
||||
|
||||
// NewResourceRG is a helper function to simplify the provider implementation.
|
||||
func NewResourceRG() resource.Resource {
|
||||
return &resourceRG{}
|
||||
}
|
||||
|
||||
// resourceRG is the resource implementation.
|
||||
type resourceRG struct {
|
||||
client *decort.DecortClient
|
||||
}
|
||||
|
||||
// Create creates the resource and sets the initial Terraform state.
|
||||
func (r *resourceRG) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
// Get plan to create resource group
|
||||
var plan models.ResourceRGModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
tflog.Error(ctx, "Create resourceRG: Error receiving the plan")
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Create resourceRG: got plan successfully", map[string]any{"name": plan.Name.ValueString()})
|
||||
tflog.Info(ctx, "Create resourceRG: start creating", map[string]any{"name": plan.Name.ValueString()})
|
||||
|
||||
// Set timeouts
|
||||
createTimeout, diags := plan.Timeouts.Create(ctx, constants.Timeout600s)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
tflog.Error(ctx, "Create resourceRG: Error set timeout")
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Create resourceRG: set timeouts successfully", map[string]any{
|
||||
"name": plan.Name.ValueString(),
|
||||
"createTimeout": createTimeout})
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, createTimeout)
|
||||
defer cancel()
|
||||
|
||||
// Check if input values are valid in the platform
|
||||
tflog.Info(ctx, "Create resourceRG: starting input checks", map[string]any{"name": plan.Name.ValueString()})
|
||||
resp.Diagnostics.Append(resourceRgInputChecks(ctx, &plan, r.client)...)
|
||||
if diags.HasError() {
|
||||
tflog.Error(ctx, "Create resourceRG: Error input checks")
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Create resourceRG: input checks successful", map[string]any{"name": plan.Name.ValueString()})
|
||||
|
||||
// Make create request and get response
|
||||
createReq, diags := utilities.CreateRequestResourceRG(ctx, &plan)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if diags.HasError() {
|
||||
tflog.Error(ctx, "Create resourceRG: Error response for create request of resource rg")
|
||||
return
|
||||
}
|
||||
|
||||
tflog.Info(ctx, "Create resourceRG: before call CloudBroker().RG().Create", map[string]any{"req": createReq})
|
||||
rgId, err := r.client.CloudBroker().RG().Create(ctx, createReq)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError(
|
||||
"Create resourceRG: unable to Create RG",
|
||||
err.Error(),
|
||||
)
|
||||
return
|
||||
}
|
||||
plan.Id = types.StringValue(strconv.Itoa(int(rgId)))
|
||||
tflog.Info(ctx, "Create resourceRG: resource group created", map[string]any{"rgId": rgId, "name": plan.Name.ValueString()})
|
||||
|
||||
// additional settings after rg creation: in case of failures, warnings are added to resp.Diagnostics,
|
||||
// because additional settings failure is not critical. If errors were added instead of warnings, terraform
|
||||
// framework would mark resource as tainted and delete it, which would be unwanted behaviour.
|
||||
|
||||
// update compute features if needed, warnings added to resp.Diagnostics in case of failure
|
||||
if !plan.ComputeFeatures.IsUnknown() { // ComputeFeatures is optional && computed
|
||||
diags := utilities.UpdateComputeFeature(ctx, rgId, &plan, r.client)
|
||||
for _, d := range diags {
|
||||
if d.Severity() == diag.SeverityError {
|
||||
resp.Diagnostics.AddWarning(d.Summary(), d.Detail())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// grant access to resource group if needed, warnings added to resp.Diagnostics in case of failure.
|
||||
resp.Diagnostics.Append(utilities.AccessCreateRG(ctx, rgId, &plan, r.client)...)
|
||||
|
||||
// set def_net for resource group if needed, warnings added to resp.Diagnostics in case of failure.
|
||||
resp.Diagnostics.Append(utilities.SetDefNetCreateRG(ctx, rgId, &plan, r.client)...)
|
||||
|
||||
// set compute allocation parameter if needed, warnings added to resp.Diagnostics in case of failure
|
||||
if !plan.CPUAllocationParameter.IsUnknown() { // CPUAllocationParameter is optional && computed
|
||||
diags := utilities.UpdateCpuAllocationParameter(ctx, rgId, &plan, r.client)
|
||||
for _, d := range diags {
|
||||
if d.Severity() == diag.SeverityError {
|
||||
resp.Diagnostics.AddWarning(d.Summary(), d.Detail())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set compute allocation ratio if needed, warnings added to resp.Diagnostics in case of failure
|
||||
if !plan.CPUAllocationRatio.IsUnknown() { // CPUAllocationParameter is optional && computed
|
||||
diags := utilities.UpdateCpuAllocationRatio(ctx, rgId, &plan, r.client)
|
||||
for _, d := range diags {
|
||||
if d.Severity() == diag.SeverityError {
|
||||
resp.Diagnostics.AddWarning(d.Summary(), d.Detail())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// enable/disable of resource group after creation, warnings added to resp.Diagnostics in case of failure.
|
||||
resp.Diagnostics.Append(utilities.EnableDisableCreateRG(ctx, rgId, &plan, r.client)...)
|
||||
|
||||
tflog.Info(ctx, "Create resourceRG: resource creation is completed", map[string]any{"rg_id": rgId})
|
||||
|
||||
// Map response body to schema and populate Computed attribute values
|
||||
resp.Diagnostics.Append(flattens.RGResource(ctx, &plan, r.client)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Set data last update
|
||||
plan.LastUpdated = types.StringValue(time.Now().Format(time.RFC850))
|
||||
|
||||
// Set state to fully populated data
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Read refreshes the Terraform state with the latest data.
|
||||
func (r *resourceRG) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
// Get current state
|
||||
var state models.ResourceRGModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
tflog.Error(ctx, "Read resourceRG: Error get state")
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Read resourceRG: got state successfully", map[string]any{"rg_id": state.Id.ValueString()})
|
||||
|
||||
// Set timeouts
|
||||
readTimeout, diags := state.Timeouts.Read(ctx, constants.Timeout300s)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
tflog.Error(ctx, "Read resourceRG: Error set timeout")
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Read resourceRG: set timeouts successfully", map[string]any{
|
||||
"rg_id": state.Id.ValueString(),
|
||||
"readTimeout": readTimeout})
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, readTimeout)
|
||||
defer cancel()
|
||||
|
||||
// read status
|
||||
resp.Diagnostics.Append(utilities.RGReadStatus(ctx, &state, r.client)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
tflog.Error(ctx, "Read resourceRG: Error reading resource group status")
|
||||
return
|
||||
}
|
||||
|
||||
// Overwrite items with refreshed state
|
||||
resp.Diagnostics.Append(flattens.RGResource(ctx, &state, r.client)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
tflog.Error(ctx, "Read resourceRG: Error flatten resource group")
|
||||
return
|
||||
}
|
||||
|
||||
// Set refreshed state
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
tflog.Error(ctx, "Read resourceRG: Error set state")
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "End read resource group")
|
||||
}
|
||||
|
||||
// Update updates the resource and sets the updated Terraform state on success.
|
||||
func (r *resourceRG) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
// Retrieve values from plan
|
||||
var plan models.ResourceRGModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
tflog.Error(ctx, "Update resourceRG: Error receiving the plan")
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Update resourceRG: got plan successfully", map[string]any{"rg_id": plan.Id.ValueString()})
|
||||
|
||||
// Retrieve values from state
|
||||
var state models.ResourceRGModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
tflog.Error(ctx, "Update resourceRG: Error receiving the state")
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Update resourceRG: got state successfully", map[string]any{"rg_id": state.Id.ValueString()})
|
||||
|
||||
// Set timeouts
|
||||
updateTimeout, diags := plan.Timeouts.Update(ctx, constants.Timeout600s)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
tflog.Error(ctx, "Error set timeout")
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Update resourceRG: set timeouts successfully", map[string]any{
|
||||
"rg_id": state.Id.ValueString(),
|
||||
"updateTimeout": updateTimeout})
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, updateTimeout)
|
||||
defer cancel()
|
||||
|
||||
// Checking for values in the platform
|
||||
tflog.Info(ctx, "Update resourceRG: starting input checks", map[string]any{"rg_id": plan.Id.ValueString()})
|
||||
resp.Diagnostics.Append(resourceRgInputChecks(ctx, &plan, r.client)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
tflog.Error(ctx, "Update resourceRG: Error input checks")
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Update resourceRG: input checks successful", map[string]any{"rg_id": state.Id.ValueString()})
|
||||
|
||||
rgId, err := strconv.ParseUint(state.Id.ValueString(), 10, 64)
|
||||
if err != nil {
|
||||
diags.AddError("Cannot parse resource group ID from state", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Get current resource group values
|
||||
recordRG, err := utilities.RGCheckPresence(ctx, rgId, r.client)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError(
|
||||
"Update resourceRG: unable to Update RG after input checks",
|
||||
err.Error(),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
tflog.Info(ctx, "Update resourceRG: check status for RG", map[string]any{"rg_id": recordRG.ID, "status": recordRG.Status})
|
||||
|
||||
// Validate if changes in plan are allowed
|
||||
tflog.Info(ctx, "Update resourceRG: checking def_net is not empty in case of change", map[string]any{
|
||||
"rg_id": state.Id.ValueString()})
|
||||
if !state.DefNet.IsNull() && plan.DefNet.IsNull() {
|
||||
resp.Diagnostics.AddError(
|
||||
"Update resourceRG: Invalid input provided",
|
||||
fmt.Sprintf("block def_net must not be empty for resource with rg_id %d", recordRG.ID),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
tflog.Info(ctx, "Update resourceRG: checking def_net_type, ipcidr, ext_ip are not changed", map[string]any{
|
||||
"rg_id": state.Id.ValueString(),
|
||||
"def_net_type_plan": plan.DefNetType.ValueString(),
|
||||
"def_net_type_state": state.DefNetType.ValueString(),
|
||||
"ipcidr_plan": plan.IPCIDR.ValueString(),
|
||||
"ipcidr_state": state.IPCIDR.ValueString(),
|
||||
"ext_ip_plan": plan.ExtIP.ValueString(),
|
||||
"ext_ip_state": state.ExtIP.ValueString(),
|
||||
})
|
||||
|
||||
if !plan.DefNetType.Equal(state.DefNetType) {
|
||||
resp.Diagnostics.AddError(
|
||||
"Update resourceRG: Invalid input provided. Warning can be ignored if resource was imported.",
|
||||
fmt.Sprintf("block def_net_type must not be changed for resource with rg_id %d", recordRG.ID),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if !plan.IPCIDR.Equal(state.IPCIDR) {
|
||||
resp.Diagnostics.AddError(
|
||||
"Update resourceRG: Invalid input provided",
|
||||
fmt.Sprintf("block ipcidr must not be changed for resource with rg_id %d", recordRG.ID),
|
||||
)
|
||||
return
|
||||
}
|
||||
if !plan.ExtIP.Equal(state.ExtIP) {
|
||||
resp.Diagnostics.AddError(
|
||||
"Update resourceRG: Invalid input provided",
|
||||
fmt.Sprintf("block ext_ip must not be changed for resource with rg_id %d", recordRG.ID),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if !plan.ExtNetID.Equal(state.ExtNetID) {
|
||||
resp.Diagnostics.AddError(
|
||||
"Update resourceRG: Invalid input provided",
|
||||
fmt.Sprintf("block ext_net_id must not be changed for resource with rg_id %d", recordRG.ID),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// update RG if any of the fields name, description, register_computes or quota has been changed
|
||||
resp.Diagnostics.Append(utilities.UpdateRG(ctx, rgId, &plan, &state, r.client)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
tflog.Error(ctx, "Update resourceRG: Error updating rg")
|
||||
return
|
||||
}
|
||||
|
||||
// grant/revoke access for RG
|
||||
if !reflect.DeepEqual(plan.Access, state.Access) {
|
||||
resp.Diagnostics.Append(utilities.AccessUpdateRG(ctx, rgId, &plan, &state, r.client)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
tflog.Error(ctx, "Update resourceRG: Error grant/revoke access for rg")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// set new def_net is needed
|
||||
if !reflect.DeepEqual(plan.DefNet, state.DefNet) {
|
||||
resp.Diagnostics.Append(utilities.SetDefNetUpdateRG(ctx, rgId, &plan, &state, r.client)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
tflog.Error(ctx, "Update resourceRG: Unable to setDefNet for RG")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// set new cpu allocation parameter is needed
|
||||
if !plan.CPUAllocationParameter.IsUnknown() && !plan.CPUAllocationParameter.Equal(state.CPUAllocationParameter) {
|
||||
resp.Diagnostics.Append(utilities.UpdateCpuAllocationParameter(ctx, rgId, &plan, r.client)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
tflog.Error(ctx, "Update resourceRG: Unable to setDefNet for RG")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// set new cpu allocation ratio is needed
|
||||
if !plan.CPUAllocationRatio.IsUnknown() && !plan.CPUAllocationRatio.Equal(state.CPUAllocationRatio) {
|
||||
resp.Diagnostics.Append(utilities.UpdateCpuAllocationRatio(ctx, rgId, &plan, r.client)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
tflog.Error(ctx, "Update resourceRG: Unable to setDefNet for RG")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// enable or disable RG
|
||||
if !plan.Enable.Equal(state.Enable) {
|
||||
resp.Diagnostics.Append(utilities.EnableDisableUpdateRG(ctx, rgId, &plan, r.client)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
tflog.Error(ctx, "Update resourceRG: Error enable/disable rg")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
tflog.Info(ctx, "Update resourceRG: resource update is completed", map[string]any{"rg_id": plan.Id.ValueString()})
|
||||
|
||||
// Map response body to schema and populate Computed attribute values
|
||||
resp.Diagnostics.Append(flattens.RGResource(ctx, &plan, r.client)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Set data last update
|
||||
plan.LastUpdated = types.StringValue(time.Now().Format(time.RFC850))
|
||||
|
||||
// Set state to fully populated data
|
||||
diags = resp.State.Set(ctx, plan)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Delete deletes the resource and removes the Terraform state on success.
|
||||
func (r *resourceRG) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
// Get current state
|
||||
var state models.ResourceRGModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
tflog.Error(ctx, "Delete resourceRG: Error get state")
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Delete resourceRG: got state successfully", map[string]any{"rg_id": state.Id.ValueString()})
|
||||
|
||||
// Set timeouts
|
||||
deleteTimeout, diags := state.Timeouts.Delete(ctx, constants.Timeout300s)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
tflog.Error(ctx, "Delete resourceRG: Error set timeout")
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Delete resourceRG: set timeouts successfully", map[string]any{
|
||||
"rg_id": state.Id.ValueString(),
|
||||
"deleteTimeout": deleteTimeout})
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, deleteTimeout)
|
||||
defer cancel()
|
||||
|
||||
// Delete existing resource group
|
||||
delReq := rg.DeleteRequest{
|
||||
RGID: uint64(state.RGID.ValueInt64()),
|
||||
Force: state.Force.ValueBool(),
|
||||
Permanently: state.Permanently.ValueBool(),
|
||||
}
|
||||
|
||||
_, err := r.client.CloudAPI().RG().Delete(ctx, delReq)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Delete resourceRG: Error deleting resource group with error: ", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
tflog.Info(ctx, "End delete resource group", map[string]any{"rg_id": state.Id.ValueString()})
|
||||
}
|
||||
|
||||
// Schema defines the schema for the resource.
|
||||
func (r *resourceRG) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
Attributes: schemas.MakeSchemaResourceRG(),
|
||||
Blocks: map[string]schema.Block{
|
||||
"timeouts": timeouts.Block(ctx, timeouts.Opts{Create: true, Read: true, Update: true, Delete: true}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Metadata returns the resource type name.
|
||||
func (r *resourceRG) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_cb_rg"
|
||||
}
|
||||
|
||||
// Configure adds the provider configured client to the resource.
|
||||
func (r *resourceRG) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
||||
tflog.Info(ctx, "Get Configure resourceRG")
|
||||
r.client = client.Resource(ctx, &req, resp)
|
||||
tflog.Info(ctx, "Getting Configure resourceRG successfully")
|
||||
}
|
||||
|
||||
func (r *resourceRG) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||
// Retrieve import ID and save to id attribute
|
||||
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
|
||||
}
|
||||
@@ -13,12 +13,6 @@ func MakeSchemaDataSourceRG() map[string]schema.Attribute {
|
||||
Description: "resource group id",
|
||||
},
|
||||
|
||||
// optional attributes
|
||||
"reason": schema.StringAttribute{
|
||||
Optional: true,
|
||||
Description: "reason for request",
|
||||
},
|
||||
|
||||
//computed attributes
|
||||
"account_id": schema.Int64Attribute{
|
||||
Computed: true,
|
||||
|
||||
@@ -12,12 +12,6 @@ func MakeSchemaDataSourceRGUsage() map[string]schema.Attribute {
|
||||
Description: "find by rg id",
|
||||
},
|
||||
|
||||
// optional attributes
|
||||
"reason": schema.StringAttribute{
|
||||
Optional: true,
|
||||
Description: "reason for action",
|
||||
},
|
||||
|
||||
//computed attributes
|
||||
"id": schema.StringAttribute{
|
||||
Computed: true,
|
||||
|
||||
290
internal/service/cloudbroker/rg/schemas/schema_resource_rg.go
Normal file
290
internal/service/cloudbroker/rg/schemas/schema_resource_rg.go
Normal file
@@ -0,0 +1,290 @@
|
||||
package schemas
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
func MakeSchemaResourceRG() map[string]schema.Attribute {
|
||||
return map[string]schema.Attribute{
|
||||
// required attributes
|
||||
"account_id": schema.Int64Attribute{
|
||||
Required: true,
|
||||
Description: "account, which will own this resource group",
|
||||
},
|
||||
"gid": schema.Int64Attribute{
|
||||
Required: true,
|
||||
Description: "grid id",
|
||||
},
|
||||
"rg_name": schema.StringAttribute{
|
||||
Required: true,
|
||||
Description: "name of this RG. Must be unique within the account.",
|
||||
},
|
||||
|
||||
// optional attributes
|
||||
"def_net_type": schema.StringAttribute{
|
||||
// attribute is only Optional (not Computed) on purpose. If Computed is added, errors occur during Create and
|
||||
// Update of the resource in case "def_net"."net_type" is different from "def_net_type". Terraform framework
|
||||
// produces "Error: Provider produced inconsistent results after apply" if plan values and resulting values
|
||||
// are different, and this is exactly what happens if Computed and Optional field can be updated indirectly
|
||||
// (via "def_net","net_type" in our case).
|
||||
Optional: true,
|
||||
Description: "type of the default network for this RG. VMs created in this RG will be by default connected to this network. Allowed values are PRIVATE, PUBLIC, NONE.",
|
||||
Validators: []validator.String{
|
||||
stringvalidator.OneOf("PRIVATE", "PUBLIC", "NONE"), // case is not ignored
|
||||
},
|
||||
// default value is "PRIVATE".
|
||||
},
|
||||
"ipcidr": schema.StringAttribute{
|
||||
Optional: true,
|
||||
Description: "private network IP CIDR if default network PRIVATE",
|
||||
},
|
||||
"ext_net_id": schema.Int64Attribute{
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Description: "external network id",
|
||||
Default: int64default.StaticInt64(0),
|
||||
// default value is 0.
|
||||
},
|
||||
"ext_ip": schema.StringAttribute{
|
||||
Optional: true,
|
||||
Description: "IP address on the external network to request when def_net_type=PRIVATE and ext_net_id is not 0.",
|
||||
},
|
||||
"owner": schema.StringAttribute{
|
||||
Optional: true,
|
||||
Description: "username - owner of this RG. Leave blank to set current user as owner",
|
||||
},
|
||||
"resource_limits": schema.SingleNestedAttribute{
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Description: "Quota settings for this resource group.",
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"cu_c": schema.Float64Attribute{
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Description: "Limit on the total number of CPUs in this resource group.",
|
||||
},
|
||||
"cu_d": schema.Float64Attribute{
|
||||
Computed: true,
|
||||
},
|
||||
"cu_dm": schema.Float64Attribute{
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Description: "Limit on the total volume of storage resources in this resource group, specified in GB.",
|
||||
},
|
||||
"cu_i": schema.Float64Attribute{
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Description: "Limit on the total number of external IP addresses this resource group can use.",
|
||||
},
|
||||
"cu_m": schema.Float64Attribute{
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Description: "Limit on the total amount of RAM in this resource group, specified in MB.",
|
||||
},
|
||||
"cu_np": schema.Float64Attribute{
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Description: "Limit on the total ingress network traffic for this resource group, specified in GB.",
|
||||
},
|
||||
"gpu_units": schema.Float64Attribute{
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
"access": schema.ListNestedAttribute{
|
||||
Optional: true,
|
||||
Description: "Grant/revoke user or group access to the Resource group as specified",
|
||||
NestedObject: schema.NestedAttributeObject{
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"user": schema.StringAttribute{
|
||||
Required: true,
|
||||
Description: "User or group name to grant access",
|
||||
},
|
||||
"right": schema.StringAttribute{
|
||||
Required: true,
|
||||
Description: "Access rights to set, one of 'R', 'RCX' or 'ARCXDU'",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"def_net": schema.SingleNestedAttribute{
|
||||
Optional: true,
|
||||
Description: "Set default network for attach associated VMs",
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"net_type": schema.StringAttribute{
|
||||
Required: true,
|
||||
Validators: []validator.String{
|
||||
stringvalidator.OneOf("PRIVATE", "PUBLIC"), // case is not ignored
|
||||
},
|
||||
Description: "Network type to set. Must be on of 'PRIVATE' or 'PUBLIC'.",
|
||||
},
|
||||
"net_id": schema.Int64Attribute{
|
||||
Optional: true,
|
||||
Description: "Network segment ID. If netType is PUBLIC and netId is 0 then default external network segment will be selected. If netType is PRIVATE and netId=0, the first ViNS defined for this RG will be selected. Otherwise, netId identifies either existing external network segment or ViNS.",
|
||||
// default value is 0
|
||||
},
|
||||
},
|
||||
},
|
||||
"description": schema.StringAttribute{
|
||||
Optional: true,
|
||||
Description: "User-defined text description of this resource group.",
|
||||
},
|
||||
"force": schema.BoolAttribute{
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Description: "Set to True if you want force delete non-empty RG",
|
||||
Default: booldefault.StaticBool(true),
|
||||
// default value is true
|
||||
},
|
||||
"permanently": schema.BoolAttribute{
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Description: "Set to True if you want force delete non-empty RG",
|
||||
Default: booldefault.StaticBool(true),
|
||||
// default value is true
|
||||
},
|
||||
"register_computes": schema.BoolAttribute{
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Description: "Register computes in registration system",
|
||||
Default: booldefault.StaticBool(false),
|
||||
// default value is false
|
||||
},
|
||||
"restore": schema.BoolAttribute{
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Default: booldefault.StaticBool(true),
|
||||
// default value is true
|
||||
},
|
||||
"enable": schema.BoolAttribute{
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Description: "flag for enable/disable RG",
|
||||
Default: booldefault.StaticBool(true),
|
||||
// default value is true
|
||||
},
|
||||
"uniq_pools": schema.ListAttribute{
|
||||
Optional: true,
|
||||
ElementType: types.StringType,
|
||||
Description: "List of strings with pools i.e.: [\"sep1_poolName1\", \"sep2_poolName2\"]",
|
||||
},
|
||||
"cpu_allocation_parameter": schema.StringAttribute{
|
||||
Computed: true,
|
||||
Optional: true,
|
||||
Description: "set cpu allocation parameter",
|
||||
Validators: []validator.String{
|
||||
stringvalidator.OneOf("strict", "loose"), // case is not ignored
|
||||
},
|
||||
},
|
||||
"cpu_allocation_ratio": schema.Float64Attribute{
|
||||
Computed: true,
|
||||
Optional: true,
|
||||
Description: "set cpu allocation ratio",
|
||||
},
|
||||
"compute_features": schema.ListAttribute{
|
||||
Optional: true,
|
||||
ElementType: types.StringType,
|
||||
Validators: []validator.List{
|
||||
listvalidator.ValueStringsAre(stringvalidator.OneOf("hugepages", "numa", "cpupin", "vfnic")),
|
||||
},
|
||||
},
|
||||
|
||||
// computed attributes
|
||||
"rg_id": schema.Int64Attribute{
|
||||
Computed: true,
|
||||
},
|
||||
"last_updated": schema.StringAttribute{
|
||||
Computed: true,
|
||||
},
|
||||
"account_name": schema.StringAttribute{
|
||||
Computed: true,
|
||||
},
|
||||
"acl": schema.ListNestedAttribute{
|
||||
Computed: true,
|
||||
NestedObject: schema.NestedAttributeObject{
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"explicit": schema.BoolAttribute{
|
||||
Computed: true,
|
||||
},
|
||||
"guid": schema.StringAttribute{
|
||||
Computed: true,
|
||||
},
|
||||
"right": schema.StringAttribute{
|
||||
Computed: true,
|
||||
},
|
||||
"status": schema.StringAttribute{
|
||||
Computed: true,
|
||||
},
|
||||
"type": schema.StringAttribute{
|
||||
Computed: true,
|
||||
},
|
||||
"user_group_id": schema.StringAttribute{
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"created_by": schema.StringAttribute{
|
||||
Computed: true,
|
||||
},
|
||||
"created_time": schema.Int64Attribute{
|
||||
Computed: true,
|
||||
},
|
||||
"def_net_id": schema.Int64Attribute{
|
||||
Computed: true,
|
||||
},
|
||||
"deleted_by": schema.StringAttribute{
|
||||
Computed: true,
|
||||
},
|
||||
"deleted_time": schema.Int64Attribute{
|
||||
Computed: true,
|
||||
},
|
||||
"guid": schema.Int64Attribute{
|
||||
Computed: true,
|
||||
},
|
||||
"id": schema.StringAttribute{
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
},
|
||||
"lock_status": schema.StringAttribute{
|
||||
Computed: true,
|
||||
},
|
||||
"milestones": schema.Int64Attribute{
|
||||
Computed: true,
|
||||
},
|
||||
"secret": schema.StringAttribute{
|
||||
Computed: true,
|
||||
},
|
||||
"status": schema.StringAttribute{
|
||||
Computed: true,
|
||||
},
|
||||
"updated_by": schema.StringAttribute{
|
||||
Computed: true,
|
||||
},
|
||||
"updated_time": schema.Int64Attribute{
|
||||
Computed: true,
|
||||
},
|
||||
"resource_types": schema.ListAttribute{
|
||||
Computed: true,
|
||||
ElementType: types.StringType,
|
||||
},
|
||||
"vins": schema.ListAttribute{
|
||||
Computed: true,
|
||||
ElementType: types.Int64Type,
|
||||
},
|
||||
"vms": schema.ListAttribute{
|
||||
Computed: true,
|
||||
ElementType: types.Int64Type,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,6 @@ import (
|
||||
func RGUsageCheckPresence(ctx context.Context, plan *models.DataSourceRGUsageModel, c *decort.DecortClient) (*rg.Reservation, error) {
|
||||
usageReq := rg.UsageRequest{RGID: uint64(plan.RGID.ValueInt64())}
|
||||
|
||||
if !plan.Reason.IsNull() {
|
||||
usageReq.Reason = plan.Reason.ValueString()
|
||||
}
|
||||
|
||||
tflog.Info(ctx, "RGUsageCheckPresence: before call CloudBroker().RG().Usage", map[string]any{"response": usageReq})
|
||||
usage, err := c.CloudBroker().RG().Usage(ctx, usageReq)
|
||||
if err != nil {
|
||||
|
||||
751
internal/service/cloudbroker/rg/utilities/utility_resource_rg.go
Normal file
751
internal/service/cloudbroker/rg/utilities/utility_resource_rg.go
Normal file
@@ -0,0 +1,751 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user