SKE Cluster: fix extensions and hibernations mapping (#135)

* Fix bug in extensions.argus conversion

* Fix mapExtensions

* Fix extensions mapping, add test case

* Check disabled argus or acl independently

* Add last fixes

* Replace NewListValueFrom with NewListValue

* Remove unused argument
This commit is contained in:
Vicente Pinto 2023-11-08 16:01:35 +00:00 committed by GitHub
parent cbbf3a00c5
commit d1b10d23c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 254 additions and 21 deletions

View file

@ -154,7 +154,7 @@ type extensions struct {
// Types corresponding to extensions
var extensionsTypes = map[string]attr.Type{
"argus": basetypes.ObjectType{AttrTypes: argusExtensionTypes},
"argus": basetypes.ObjectType{AttrTypes: argusTypes},
"acl": basetypes.ObjectType{AttrTypes: aclTypes},
}
@ -169,13 +169,13 @@ var aclTypes = map[string]attr.Type{
"allowed_cidrs": basetypes.ListType{ElemType: types.StringType},
}
type argusExtension struct {
type argus struct {
Enabled types.Bool `tfsdk:"enabled"`
ArgusInstanceId types.String `tfsdk:"argus_instance_id"`
}
// Types corresponding to argusExtension
var argusExtensionTypes = map[string]attr.Type{
var argusTypes = map[string]attr.Type{
"enabled": basetypes.BoolType{},
"argus_instance_id": basetypes.StringType{},
}
@ -490,7 +490,7 @@ func (r *clusterResource) Schema(_ context.Context, _ resource.SchemaRequest, re
},
"allowed_cidrs": schema.ListAttribute{
Description: "Specify a list of CIDRs to whitelist.",
Required: true,
Optional: true,
ElementType: types.StringType,
},
},
@ -818,16 +818,16 @@ func toExtensionsPayload(ctx context.Context, m *Cluster) (*ske.Extension, error
}
}
var skeArgusExtension *ske.Argus
var skeArgus *ske.Argus
if !(ex.Argus.IsNull() || ex.Argus.IsUnknown()) {
argus := argusExtension{}
argus := argus{}
diags = ex.Argus.As(ctx, &argus, basetypes.ObjectAsOptions{})
if diags.HasError() {
return nil, fmt.Errorf("converting extensions.argus object: %v", diags.Errors())
}
argusEnabled := conversion.BoolValueToPointer(argus.Enabled)
argusInstanceId := conversion.StringValueToPointer(argus.ArgusInstanceId)
skeArgusExtension = &ske.Argus{
skeArgus = &ske.Argus{
Enabled: argusEnabled,
ArgusInstanceId: argusInstanceId,
}
@ -835,7 +835,7 @@ func toExtensionsPayload(ctx context.Context, m *Cluster) (*ske.Extension, error
return &ske.Extension{
Acl: skeAcl,
Argus: skeArgusExtension,
Argus: skeArgus,
}, nil
}
@ -1040,11 +1040,28 @@ func mapTaints(t *[]ske.Taint, nodePool map[string]attr.Value) error {
}
func mapHibernations(cl *ske.ClusterResponse, m *Cluster) error {
if cl.Hibernation == nil || cl.Hibernation.Schedules == nil {
if cl.Hibernation == nil {
if !m.Hibernations.IsNull() {
emptyHibernations, diags := basetypes.NewListValue(basetypes.ObjectType{AttrTypes: hibernationTypes}, []attr.Value{})
if diags.HasError() {
return fmt.Errorf("hibernations is an empty list, converting to terraform empty list: %w", core.DiagsToError(diags))
}
m.Hibernations = emptyHibernations
return nil
}
m.Hibernations = basetypes.NewListNull(basetypes.ObjectType{AttrTypes: hibernationTypes})
return nil
}
if cl.Hibernation.Schedules == nil {
emptyHibernations, diags := basetypes.NewListValue(basetypes.ObjectType{AttrTypes: hibernationTypes}, []attr.Value{})
if diags.HasError() {
return fmt.Errorf("hibernations is an empty list, converting to terraform empty list: %w", core.DiagsToError(diags))
}
m.Hibernations = emptyHibernations
return nil
}
hibernations := []attr.Value{}
for i, hibernationResp := range *cl.Hibernation.Schedules {
hibernation := map[string]attr.Value{
@ -1149,14 +1166,73 @@ func getMaintenanceTimes(ctx context.Context, cl *ske.ClusterResponse, m *Cluste
return startTime, endTime, nil
}
func checkDisabledExtensions(ctx context.Context, ex extensions) (aclDisabled, argusDisabled bool, err error) {
var diags diag.Diagnostics
acl := acl{}
if ex.ACL.IsNull() {
acl.Enabled = types.BoolValue(false)
} else {
diags = ex.ACL.As(ctx, &acl, basetypes.ObjectAsOptions{})
if diags.HasError() {
return false, false, fmt.Errorf("converting extensions.acl object: %v", diags.Errors())
}
}
argus := argus{}
if ex.Argus.IsNull() {
argus.Enabled = types.BoolValue(false)
} else {
diags = ex.Argus.As(ctx, &argus, basetypes.ObjectAsOptions{})
if diags.HasError() {
return false, false, fmt.Errorf("converting extensions.argus object: %v", diags.Errors())
}
}
return !acl.Enabled.ValueBool(), !argus.Enabled.ValueBool(), nil
}
func mapExtensions(ctx context.Context, cl *ske.ClusterResponse, m *Cluster) error {
if cl.Extensions == nil || (cl.Extensions.Argus == nil && cl.Extensions.Acl == nil) {
if cl.Extensions == nil {
m.Extensions = types.ObjectNull(extensionsTypes)
return nil
}
var diags diag.Diagnostics
acl := types.ObjectNull(aclTypes)
ex := extensions{}
if !m.Extensions.IsNull() {
diags := m.Extensions.As(ctx, &ex, basetypes.ObjectAsOptions{})
if diags.HasError() {
return fmt.Errorf("converting extensions object: %v", diags.Errors())
}
}
// If the user provides the extensions block with the enabled flags as false
// the SKE API will return an empty extensions block, which throws an inconsistent
// result after apply error. To prevent this error, if both flags are false,
// we set the fields provided by the user in the terraform model
// If the extensions field is not provided, the SKE API returns an empty object.
// If we parse that object into the terraform model, it will produce an inconsistent result after apply
// error
aclDisabled, argusDisabled, err := checkDisabledExtensions(ctx, ex)
if err != nil {
return fmt.Errorf("checking if extensions are disabled: %w", err)
}
disabledExtensions := false
if aclDisabled && argusDisabled {
disabledExtensions = true
}
emptyExtensions := &ske.Extension{}
if *cl.Extensions == *emptyExtensions && (disabledExtensions || m.Extensions.IsNull()) {
if m.Extensions.Attributes() == nil {
m.Extensions = types.ObjectNull(extensionsTypes)
}
return nil
}
aclExtension := types.ObjectNull(aclTypes)
if cl.Extensions.Acl != nil {
enabled := types.BoolNull()
if cl.Extensions.Acl.Enabled != nil {
@ -1173,13 +1249,15 @@ func mapExtensions(ctx context.Context, cl *ske.ClusterResponse, m *Cluster) err
"allowed_cidrs": cidrsList,
}
acl, diags = types.ObjectValue(aclTypes, aclValues)
aclExtension, diags = types.ObjectValue(aclTypes, aclValues)
if diags.HasError() {
return fmt.Errorf("creating acl: %w", core.DiagsToError(diags))
}
} else if aclDisabled && !ex.ACL.IsNull() {
aclExtension = ex.ACL
}
argusExtension := types.ObjectNull(argusExtensionTypes)
argusExtension := types.ObjectNull(argusTypes)
if cl.Extensions.Argus != nil {
enabled := types.BoolNull()
if cl.Extensions.Argus.Enabled != nil {
@ -1196,14 +1274,16 @@ func mapExtensions(ctx context.Context, cl *ske.ClusterResponse, m *Cluster) err
"argus_instance_id": argusInstanceId,
}
argusExtension, diags = types.ObjectValue(argusExtensionTypes, argusExtensionValues)
argusExtension, diags = types.ObjectValue(argusTypes, argusExtensionValues)
if diags.HasError() {
return fmt.Errorf("creating argus extension: %w", core.DiagsToError(diags))
}
} else if argusDisabled && !ex.Argus.IsNull() {
argusExtension = ex.Argus
}
extensionsValues := map[string]attr.Value{
"acl": acl,
"acl": aclExtension,
"argus": argusExtension,
}

View file

@ -15,13 +15,15 @@ import (
func TestMapFields(t *testing.T) {
cs := ske.ClusterStatusState("OK")
tests := []struct {
description string
input *ske.ClusterResponse
expected Cluster
isValid bool
description string
stateExtensions types.Object
input *ske.ClusterResponse
expected Cluster
isValid bool
}{
{
"default_values",
types.ObjectNull(extensionsTypes),
&ske.ClusterResponse{
Name: utils.Ptr("name"),
},
@ -41,6 +43,7 @@ func TestMapFields(t *testing.T) {
},
{
"simple_values",
types.ObjectNull(extensionsTypes),
&ske.ClusterResponse{
Extensions: &ske.Extension{
Acl: &ske.ACL{
@ -195,7 +198,7 @@ func TestMapFields(t *testing.T) {
types.StringValue("cidr1"),
}),
}),
"argus": types.ObjectValueMust(argusExtensionTypes, map[string]attr.Value{
"argus": types.ObjectValueMust(argusTypes, map[string]attr.Value{
"enabled": types.BoolValue(true),
"argus_instance_id": types.StringValue("aid"),
}),
@ -204,14 +207,163 @@ func TestMapFields(t *testing.T) {
},
true,
},
{
"extensions_mixed_values",
types.ObjectNull(extensionsTypes),
&ske.ClusterResponse{
Extensions: &ske.Extension{
Acl: &ske.ACL{
AllowedCidrs: nil,
Enabled: utils.Ptr(true),
},
Argus: &ske.Argus{
ArgusInstanceId: nil,
Enabled: utils.Ptr(true),
},
},
Name: utils.Ptr("name"),
},
Cluster{
Id: types.StringValue("pid,name"),
ProjectId: types.StringValue("pid"),
Name: types.StringValue("name"),
KubernetesVersion: types.StringNull(),
AllowPrivilegedContainers: types.BoolNull(),
NodePools: types.ListNull(types.ObjectType{AttrTypes: nodePoolTypes}),
Maintenance: types.ObjectNull(maintenanceTypes),
Hibernations: types.ListNull(types.ObjectType{AttrTypes: hibernationTypes}),
Extensions: types.ObjectValueMust(extensionsTypes, map[string]attr.Value{
"acl": types.ObjectValueMust(aclTypes, map[string]attr.Value{
"enabled": types.BoolValue(true),
"allowed_cidrs": types.ListNull(types.StringType),
}),
"argus": types.ObjectValueMust(argusTypes, map[string]attr.Value{
"enabled": types.BoolValue(true),
"argus_instance_id": types.StringNull(),
}),
}),
KubeConfig: types.StringNull(),
},
true,
},
{
"extensions_disabled",
types.ObjectValueMust(extensionsTypes, map[string]attr.Value{
"acl": types.ObjectValueMust(aclTypes, map[string]attr.Value{
"enabled": types.BoolValue(false),
"allowed_cidrs": types.ListNull(types.StringType),
}),
"argus": types.ObjectValueMust(argusTypes, map[string]attr.Value{
"enabled": types.BoolValue(false),
"argus_instance_id": types.StringNull(),
}),
}),
&ske.ClusterResponse{
Extensions: &ske.Extension{},
Name: utils.Ptr("name"),
},
Cluster{
Id: types.StringValue("pid,name"),
ProjectId: types.StringValue("pid"),
Name: types.StringValue("name"),
KubernetesVersion: types.StringNull(),
AllowPrivilegedContainers: types.BoolNull(),
NodePools: types.ListNull(types.ObjectType{AttrTypes: nodePoolTypes}),
Maintenance: types.ObjectNull(maintenanceTypes),
Hibernations: types.ListNull(types.ObjectType{AttrTypes: hibernationTypes}),
Extensions: types.ObjectValueMust(extensionsTypes, map[string]attr.Value{
"acl": types.ObjectValueMust(aclTypes, map[string]attr.Value{
"enabled": types.BoolValue(false),
"allowed_cidrs": types.ListNull(types.StringType),
}),
"argus": types.ObjectValueMust(argusTypes, map[string]attr.Value{
"enabled": types.BoolValue(false),
"argus_instance_id": types.StringNull(),
}),
}),
KubeConfig: types.StringNull(),
},
true,
},
{
"extensions_only_argus_disabled",
types.ObjectValueMust(extensionsTypes, map[string]attr.Value{
"acl": types.ObjectValueMust(aclTypes, map[string]attr.Value{
"enabled": types.BoolValue(true),
"allowed_cidrs": types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("cidr1"),
}),
}),
"argus": types.ObjectValueMust(argusTypes, map[string]attr.Value{
"enabled": types.BoolValue(false),
"argus_instance_id": types.StringValue("id"),
}),
}),
&ske.ClusterResponse{
Extensions: &ske.Extension{
Acl: &ske.ACL{
AllowedCidrs: &[]string{"cidr1"},
Enabled: utils.Ptr(true),
},
},
Name: utils.Ptr("name"),
},
Cluster{
Id: types.StringValue("pid,name"),
ProjectId: types.StringValue("pid"),
Name: types.StringValue("name"),
KubernetesVersion: types.StringNull(),
AllowPrivilegedContainers: types.BoolNull(),
NodePools: types.ListNull(types.ObjectType{AttrTypes: nodePoolTypes}),
Maintenance: types.ObjectNull(maintenanceTypes),
Hibernations: types.ListNull(types.ObjectType{AttrTypes: hibernationTypes}),
Extensions: types.ObjectValueMust(extensionsTypes, map[string]attr.Value{
"acl": types.ObjectValueMust(aclTypes, map[string]attr.Value{
"enabled": types.BoolValue(true),
"allowed_cidrs": types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("cidr1"),
}),
}),
"argus": types.ObjectValueMust(argusTypes, map[string]attr.Value{
"enabled": types.BoolValue(false),
"argus_instance_id": types.StringValue("id"),
}),
}),
KubeConfig: types.StringNull(),
},
true,
},
{
"extensions_not_set",
types.ObjectNull(extensionsTypes),
&ske.ClusterResponse{
Extensions: &ske.Extension{},
Name: utils.Ptr("name"),
},
Cluster{
Id: types.StringValue("pid,name"),
ProjectId: types.StringValue("pid"),
Name: types.StringValue("name"),
KubernetesVersion: types.StringNull(),
AllowPrivilegedContainers: types.BoolNull(),
NodePools: types.ListNull(types.ObjectType{AttrTypes: nodePoolTypes}),
Maintenance: types.ObjectNull(maintenanceTypes),
Hibernations: types.ListNull(types.ObjectType{AttrTypes: hibernationTypes}),
Extensions: types.ObjectNull(extensionsTypes),
KubeConfig: types.StringNull(),
},
true,
},
{
"nil_response",
types.ObjectNull(extensionsTypes),
nil,
Cluster{},
false,
},
{
"no_resource_id",
types.ObjectNull(extensionsTypes),
&ske.ClusterResponse{},
Cluster{},
false,
@ -220,7 +372,8 @@ func TestMapFields(t *testing.T) {
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
state := &Cluster{
ProjectId: tt.expected.ProjectId,
ProjectId: tt.expected.ProjectId,
Extensions: tt.stateExtensions,
}
err := mapFields(context.Background(), tt.input, state)
if !tt.isValid && err == nil {