Skip to content

Commit 0d438a4

Browse files
SKE Cluster - Change Extensions field model to use types.Object (#127)
* Convert SKE Extensions field to TF model * Fix unit test * Check error * Update stackit/internal/services/ske/cluster/resource.go Co-authored-by: João Palet <joao.palet@outlook.com> * Fix error messages * Replace ListValueMust with ListValue * Replace types.ListValue with types.ListValueFrom * Add context * Add separator --------- Co-authored-by: João Palet <joao.palet@outlook.com>
1 parent 1d88c95 commit 0d438a4

File tree

2 files changed

+145
-64
lines changed

2 files changed

+145
-64
lines changed

stackit/internal/services/ske/cluster/resource.go

Lines changed: 134 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ type Cluster struct {
6363
NodePools types.List `tfsdk:"node_pools"`
6464
Maintenance types.Object `tfsdk:"maintenance"`
6565
Hibernations types.List `tfsdk:"hibernations"`
66-
Extensions *Extensions `tfsdk:"extensions"`
66+
Extensions types.Object `tfsdk:"extensions"`
6767
KubeConfig types.String `tfsdk:"kube_config"`
6868
}
6969

@@ -147,21 +147,39 @@ var hibernationTypes = map[string]attr.Type{
147147
"timezone": basetypes.StringType{},
148148
}
149149

150-
type Extensions struct {
151-
Argus *ArgusExtension `tfsdk:"argus"`
152-
ACL *ACL `tfsdk:"acl"`
150+
type extensions struct {
151+
Argus types.Object `tfsdk:"argus"`
152+
ACL types.Object `tfsdk:"acl"`
153153
}
154154

155-
type ACL struct {
155+
// Types corresponding to extensions
156+
var extensionsTypes = map[string]attr.Type{
157+
"argus": basetypes.ObjectType{AttrTypes: argusExtensionTypes},
158+
"acl": basetypes.ObjectType{AttrTypes: aclTypes},
159+
}
160+
161+
type acl struct {
156162
Enabled types.Bool `tfsdk:"enabled"`
157163
AllowedCIDRs types.List `tfsdk:"allowed_cidrs"`
158164
}
159165

160-
type ArgusExtension struct {
166+
// Types corresponding to acl
167+
var aclTypes = map[string]attr.Type{
168+
"enabled": basetypes.BoolType{},
169+
"allowed_cidrs": basetypes.ListType{ElemType: types.StringType},
170+
}
171+
172+
type argusExtension struct {
161173
Enabled types.Bool `tfsdk:"enabled"`
162174
ArgusInstanceId types.String `tfsdk:"argus_instance_id"`
163175
}
164176

177+
// Types corresponding to argusExtension
178+
var argusExtensionTypes = map[string]attr.Type{
179+
"enabled": basetypes.BoolType{},
180+
"argus_instance_id": basetypes.StringType{},
181+
}
182+
165183
// NewClusterResource is a helper function to simplify the provider implementation.
166184
func NewClusterResource() resource.Resource {
167185
return &clusterResource{}
@@ -771,28 +789,54 @@ func toHibernationsPayload(ctx context.Context, m *Cluster) (*ske.Hibernation, e
771789
}
772790

773791
func toExtensionsPayload(ctx context.Context, m *Cluster) (*ske.Extension, error) {
774-
if m.Extensions == nil {
792+
if m.Extensions.IsNull() || m.Extensions.IsUnknown() {
775793
return nil, nil
776794
}
777-
ex := &ske.Extension{}
778-
if m.Extensions.Argus != nil {
779-
ex.Argus = &ske.Argus{
780-
Enabled: conversion.BoolValueToPointer(m.Extensions.Argus.Enabled),
781-
ArgusInstanceId: conversion.StringValueToPointer(m.Extensions.Argus.ArgusInstanceId),
782-
}
795+
ex := extensions{}
796+
diags := m.Extensions.As(ctx, &ex, basetypes.ObjectAsOptions{})
797+
if diags.HasError() {
798+
return nil, fmt.Errorf("converting extensions object: %v", diags.Errors())
783799
}
784-
if m.Extensions.ACL != nil {
800+
801+
var skeAcl *ske.ACL
802+
if !(ex.ACL.IsNull() || ex.ACL.IsUnknown()) {
803+
acl := acl{}
804+
diags = ex.ACL.As(ctx, &acl, basetypes.ObjectAsOptions{})
805+
if diags.HasError() {
806+
return nil, fmt.Errorf("converting extensions.acl object: %v", diags.Errors())
807+
}
808+
aclEnabled := conversion.BoolValueToPointer(acl.Enabled)
809+
785810
cidrs := []string{}
786-
diags := m.Extensions.ACL.AllowedCIDRs.ElementsAs(ctx, &cidrs, true)
811+
diags = acl.AllowedCIDRs.ElementsAs(ctx, &cidrs, true)
787812
if diags.HasError() {
788-
return nil, fmt.Errorf("error in extension object converion %v", diags.Errors())
813+
return nil, fmt.Errorf("converting extensions.acl.cidrs object: %v", diags.Errors())
789814
}
790-
ex.Acl = &ske.ACL{
791-
Enabled: conversion.BoolValueToPointer(m.Extensions.ACL.Enabled),
815+
skeAcl = &ske.ACL{
816+
Enabled: aclEnabled,
792817
AllowedCidrs: &cidrs,
793818
}
794819
}
795-
return ex, nil
820+
821+
var skeArgusExtension *ske.Argus
822+
if !(ex.Argus.IsNull() || ex.Argus.IsUnknown()) {
823+
argus := argusExtension{}
824+
diags = ex.ACL.As(ctx, &argus, basetypes.ObjectAsOptions{})
825+
if diags.HasError() {
826+
return nil, fmt.Errorf("converting extensions.acl object: %v", diags.Errors())
827+
}
828+
argusEnabled := conversion.BoolValueToPointer(argus.Enabled)
829+
argusInstanceId := conversion.StringValueToPointer(argus.ArgusInstanceId)
830+
skeArgusExtension = &ske.Argus{
831+
Enabled: argusEnabled,
832+
ArgusInstanceId: argusInstanceId,
833+
}
834+
}
835+
836+
return &ske.Extension{
837+
Acl: skeAcl,
838+
Argus: skeArgusExtension,
839+
}, nil
796840
}
797841

798842
func toMaintenancePayload(ctx context.Context, m *Cluster) (*ske.Maintenance, error) {
@@ -803,7 +847,7 @@ func toMaintenancePayload(ctx context.Context, m *Cluster) (*ske.Maintenance, er
803847
maintenance := Maintenance{}
804848
diags := m.Maintenance.As(ctx, &maintenance, basetypes.ObjectAsOptions{})
805849
if diags.HasError() {
806-
return nil, fmt.Errorf("error in maintenance object conversion %v", diags.Errors())
850+
return nil, fmt.Errorf("converting maintenance object: %v", diags.Errors())
807851
}
808852

809853
var timeWindowStart *string
@@ -870,7 +914,7 @@ func mapFields(ctx context.Context, cl *ske.ClusterResponse, m *Cluster) error {
870914
m.AllowPrivilegedContainers = types.BoolPointerValue(cl.Kubernetes.AllowPrivilegedContainers)
871915
}
872916

873-
err := mapNodePools(cl, m)
917+
err := mapNodePools(ctx, cl, m)
874918
if err != nil {
875919
return fmt.Errorf("mapping node_pools: %w", err)
876920
}
@@ -882,11 +926,14 @@ func mapFields(ctx context.Context, cl *ske.ClusterResponse, m *Cluster) error {
882926
if err != nil {
883927
return fmt.Errorf("mapping hibernations: %w", err)
884928
}
885-
mapExtensions(cl, m)
929+
err = mapExtensions(ctx, cl, m)
930+
if err != nil {
931+
return fmt.Errorf("mapping extensions: %w", err)
932+
}
886933
return nil
887934
}
888935

889-
func mapNodePools(cl *ske.ClusterResponse, m *Cluster) error {
936+
func mapNodePools(ctx context.Context, cl *ske.ClusterResponse, m *Cluster) error {
890937
if cl.Nodepools == nil {
891938
m.NodePools = types.ListNull(types.ObjectType{AttrTypes: nodePoolTypes})
892939
return nil
@@ -941,11 +988,7 @@ func mapNodePools(cl *ske.ClusterResponse, m *Cluster) error {
941988
}
942989

943990
if nodePoolResp.AvailabilityZones != nil {
944-
elems := []attr.Value{}
945-
for _, v := range *nodePoolResp.AvailabilityZones {
946-
elems = append(elems, types.StringValue(v))
947-
}
948-
elemsTF, diags := types.ListValue(types.StringType, elems)
991+
elemsTF, diags := types.ListValueFrom(ctx, types.StringType, *nodePoolResp.AvailabilityZones)
949992
if diags.HasError() {
950993
return fmt.Errorf("mapping index %d, field availability_zones: %w", i, core.DiagsToError(diags))
951994
}
@@ -1026,7 +1069,7 @@ func mapHibernations(cl *ske.ClusterResponse, m *Cluster) error {
10261069
}
10271070

10281071
func mapMaintenance(ctx context.Context, cl *ske.ClusterResponse, m *Cluster) error {
1029-
// Aligned with SKE team that a flattened data structure is fine, because not extensions are planned.
1072+
// Aligned with SKE team that a flattened data structure is fine, because no extensions are planned.
10301073
if cl.Maintenance == nil {
10311074
m.Maintenance = types.ObjectNull(maintenanceTypes)
10321075
return nil
@@ -1041,7 +1084,7 @@ func mapMaintenance(ctx context.Context, cl *ske.ClusterResponse, m *Cluster) er
10411084
}
10421085
startTime, endTime, err := getMaintenanceTimes(ctx, cl, m)
10431086
if err != nil {
1044-
return fmt.Errorf("failed to get maintenance times: %w", err)
1087+
return fmt.Errorf("getting maintenance times: %w", err)
10451088
}
10461089
maintenanceValues := map[string]attr.Value{
10471090
"enable_kubernetes_version_updates": ekvu,
@@ -1051,7 +1094,7 @@ func mapMaintenance(ctx context.Context, cl *ske.ClusterResponse, m *Cluster) er
10511094
}
10521095
maintenanceObject, diags := types.ObjectValue(maintenanceTypes, maintenanceValues)
10531096
if diags.HasError() {
1054-
return fmt.Errorf("failed to create flavor: %w", core.DiagsToError(diags))
1097+
return fmt.Errorf("creating flavor: %w", core.DiagsToError(diags))
10551098
}
10561099
m.Maintenance = maintenanceObject
10571100
return nil
@@ -1060,11 +1103,11 @@ func mapMaintenance(ctx context.Context, cl *ske.ClusterResponse, m *Cluster) er
10601103
func getMaintenanceTimes(ctx context.Context, cl *ske.ClusterResponse, m *Cluster) (startTime, endTime string, err error) {
10611104
startTimeAPI, err := time.Parse(time.RFC3339, *cl.Maintenance.TimeWindow.Start)
10621105
if err != nil {
1063-
return "", "", fmt.Errorf("failed to parse start time '%s' from API response as RFC3339 datetime: %w", *cl.Maintenance.TimeWindow.Start, err)
1106+
return "", "", fmt.Errorf("parsing start time '%s' from API response as RFC3339 datetime: %w", *cl.Maintenance.TimeWindow.Start, err)
10641107
}
10651108
endTimeAPI, err := time.Parse(time.RFC3339, *cl.Maintenance.TimeWindow.End)
10661109
if err != nil {
1067-
return "", "", fmt.Errorf("failed to parse end time '%s' from API response as RFC3339 datetime: %w", *cl.Maintenance.TimeWindow.End, err)
1110+
return "", "", fmt.Errorf("parsing end time '%s' from API response as RFC3339 datetime: %w", *cl.Maintenance.TimeWindow.End, err)
10681111
}
10691112

10701113
if m.Maintenance.IsNull() || m.Maintenance.IsUnknown() {
@@ -1074,15 +1117,15 @@ func getMaintenanceTimes(ctx context.Context, cl *ske.ClusterResponse, m *Cluste
10741117
maintenance := &Maintenance{}
10751118
diags := m.Maintenance.As(ctx, maintenance, basetypes.ObjectAsOptions{})
10761119
if diags.HasError() {
1077-
return "", "", fmt.Errorf("error in maintenance object conversion %w", core.DiagsToError(diags.Errors()))
1120+
return "", "", fmt.Errorf("converting maintenance object %w", core.DiagsToError(diags.Errors()))
10781121
}
10791122

10801123
if maintenance.Start.IsNull() || maintenance.Start.IsUnknown() {
10811124
startTime = startTimeAPI.Format("15:04:05Z07:00")
10821125
} else {
10831126
startTimeTF, err := time.Parse("15:04:05Z07:00", maintenance.Start.ValueString())
10841127
if err != nil {
1085-
return "", "", fmt.Errorf("failed to parse start time '%s' from TF config as RFC time: %w", maintenance.Start.ValueString(), err)
1128+
return "", "", fmt.Errorf("parsing start time '%s' from TF config as RFC time: %w", maintenance.Start.ValueString(), err)
10861129
}
10871130
if startTimeAPI.Format("15:04:05Z07:00") != startTimeTF.Format("15:04:05Z07:00") {
10881131
return "", "", fmt.Errorf("start time '%v' from API response doesn't match start time '%v' from TF config", *cl.Maintenance.TimeWindow.Start, maintenance.Start.ValueString())
@@ -1095,7 +1138,7 @@ func getMaintenanceTimes(ctx context.Context, cl *ske.ClusterResponse, m *Cluste
10951138
} else {
10961139
endTimeTF, err := time.Parse("15:04:05Z07:00", maintenance.End.ValueString())
10971140
if err != nil {
1098-
return "", "", fmt.Errorf("failed to parse end time '%s' from TF config as RFC time: %w", maintenance.End.ValueString(), err)
1141+
return "", "", fmt.Errorf("parsing end time '%s' from TF config as RFC time: %w", maintenance.End.ValueString(), err)
10991142
}
11001143
if endTimeAPI.Format("15:04:05Z07:00") != endTimeTF.Format("15:04:05Z07:00") {
11011144
return "", "", fmt.Errorf("end time '%v' from API response doesn't match end time '%v' from TF config", *cl.Maintenance.TimeWindow.End, maintenance.End.ValueString())
@@ -1106,32 +1149,70 @@ func getMaintenanceTimes(ctx context.Context, cl *ske.ClusterResponse, m *Cluste
11061149
return startTime, endTime, nil
11071150
}
11081151

1109-
func mapExtensions(cl *ske.ClusterResponse, m *Cluster) {
1152+
func mapExtensions(ctx context.Context, cl *ske.ClusterResponse, m *Cluster) error {
11101153
if cl.Extensions == nil || (cl.Extensions.Argus == nil && cl.Extensions.Acl == nil) {
1111-
return
1154+
m.Extensions = types.ObjectNull(extensionsTypes)
1155+
return nil
11121156
}
1113-
if m.Extensions == nil {
1114-
m.Extensions = &Extensions{}
1157+
1158+
var diags diag.Diagnostics
1159+
acl := types.ObjectNull(aclTypes)
1160+
if cl.Extensions.Acl != nil {
1161+
enabled := types.BoolNull()
1162+
if cl.Extensions.Acl.Enabled != nil {
1163+
enabled = types.BoolValue(*cl.Extensions.Acl.Enabled)
1164+
}
1165+
1166+
cidrsList, diags := types.ListValueFrom(ctx, types.StringType, cl.Extensions.Acl.AllowedCidrs)
1167+
if diags.HasError() {
1168+
return fmt.Errorf("creating allowed_cidrs list: %w", core.DiagsToError(diags))
1169+
}
1170+
1171+
aclValues := map[string]attr.Value{
1172+
"enabled": enabled,
1173+
"allowed_cidrs": cidrsList,
1174+
}
1175+
1176+
acl, diags = types.ObjectValue(aclTypes, aclValues)
1177+
if diags.HasError() {
1178+
return fmt.Errorf("creating acl: %w", core.DiagsToError(diags))
1179+
}
11151180
}
1181+
1182+
argusExtension := types.ObjectNull(argusExtensionTypes)
11161183
if cl.Extensions.Argus != nil {
1117-
m.Extensions.Argus = &ArgusExtension{
1118-
Enabled: types.BoolPointerValue(cl.Extensions.Argus.Enabled),
1119-
ArgusInstanceId: types.StringPointerValue(cl.Extensions.Argus.ArgusInstanceId),
1184+
enabled := types.BoolNull()
1185+
if cl.Extensions.Argus.Enabled != nil {
1186+
enabled = types.BoolValue(*cl.Extensions.Argus.Enabled)
11201187
}
1121-
}
11221188

1123-
if cl.Extensions.Acl != nil {
1124-
cidr := []attr.Value{}
1125-
if cl.Extensions.Acl.AllowedCidrs != nil {
1126-
for _, v := range *cl.Extensions.Acl.AllowedCidrs {
1127-
cidr = append(cidr, types.StringValue(v))
1128-
}
1189+
argusInstanceId := types.StringNull()
1190+
if cl.Extensions.Argus.ArgusInstanceId != nil {
1191+
argusInstanceId = types.StringValue(*cl.Extensions.Argus.ArgusInstanceId)
1192+
}
1193+
1194+
argusExtensionValues := map[string]attr.Value{
1195+
"enabled": enabled,
1196+
"argus_instance_id": argusInstanceId,
11291197
}
1130-
m.Extensions.ACL = &ACL{
1131-
Enabled: types.BoolPointerValue(cl.Extensions.Acl.Enabled),
1132-
AllowedCIDRs: types.ListValueMust(types.StringType, cidr),
1198+
1199+
argusExtension, diags = types.ObjectValue(argusExtensionTypes, argusExtensionValues)
1200+
if diags.HasError() {
1201+
return fmt.Errorf("creating argus extension: %w", core.DiagsToError(diags))
11331202
}
11341203
}
1204+
1205+
extensionsValues := map[string]attr.Value{
1206+
"acl": acl,
1207+
"argus": argusExtension,
1208+
}
1209+
1210+
extensions, diags := types.ObjectValue(extensionsTypes, extensionsValues)
1211+
if diags.HasError() {
1212+
return fmt.Errorf("creating extensions: %w", core.DiagsToError(diags))
1213+
}
1214+
m.Extensions = extensions
1215+
return nil
11351216
}
11361217

11371218
func toKubernetesPayload(m *Cluster, availableVersions []ske.KubernetesVersion) (kubernetesPayload *ske.Kubernetes, hasDeprecatedVersion bool, err error) {

stackit/internal/services/ske/cluster/resource_test.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func TestMapFields(t *testing.T) {
3434
NodePools: types.ListNull(types.ObjectType{AttrTypes: nodePoolTypes}),
3535
Maintenance: types.ObjectNull(maintenanceTypes),
3636
Hibernations: types.ListNull(types.ObjectType{AttrTypes: hibernationTypes}),
37-
Extensions: nil,
37+
Extensions: types.ObjectNull(extensionsTypes),
3838
KubeConfig: types.StringNull(),
3939
},
4040
true,
@@ -188,18 +188,18 @@ func TestMapFields(t *testing.T) {
188188
),
189189
},
190190
),
191-
Extensions: &Extensions{
192-
Argus: &ArgusExtension{
193-
Enabled: types.BoolValue(true),
194-
ArgusInstanceId: types.StringValue("aid"),
195-
},
196-
ACL: &ACL{
197-
Enabled: types.BoolValue(true),
198-
AllowedCIDRs: types.ListValueMust(types.StringType, []attr.Value{
191+
Extensions: types.ObjectValueMust(extensionsTypes, map[string]attr.Value{
192+
"acl": types.ObjectValueMust(aclTypes, map[string]attr.Value{
193+
"enabled": types.BoolValue(true),
194+
"allowed_cidrs": types.ListValueMust(types.StringType, []attr.Value{
199195
types.StringValue("cidr1"),
200196
}),
201-
},
202-
},
197+
}),
198+
"argus": types.ObjectValueMust(argusExtensionTypes, map[string]attr.Value{
199+
"enabled": types.BoolValue(true),
200+
"argus_instance_id": types.StringValue("aid"),
201+
}),
202+
}),
203203
KubeConfig: types.StringNull(),
204204
},
205205
true,

0 commit comments

Comments
 (0)