Skip to content

Commit 209e540

Browse files
fix(mariadb, opensearch, rabbitmq, redis): don't reuse resource model in datasource (#1524)
relates to #1484
1 parent 3c46b1b commit 209e540

8 files changed

Lines changed: 1256 additions & 8 deletions

File tree

stackit/internal/services/mariadb/credential/datasource.go

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,20 @@ var (
2626
_ datasource.DataSource = &credentialDataSource{}
2727
)
2828

29+
type DataSourceModel struct {
30+
Id types.String `tfsdk:"id"` // needed by TF
31+
CredentialId types.String `tfsdk:"credential_id"`
32+
InstanceId types.String `tfsdk:"instance_id"`
33+
ProjectId types.String `tfsdk:"project_id"`
34+
Host types.String `tfsdk:"host"`
35+
Hosts types.List `tfsdk:"hosts"`
36+
Name types.String `tfsdk:"name"`
37+
Password types.String `tfsdk:"password"`
38+
Port types.Int32 `tfsdk:"port"`
39+
Uri types.String `tfsdk:"uri"`
40+
Username types.String `tfsdk:"username"`
41+
}
42+
2943
// NewCredentialDataSource is a helper function to simplify the provider implementation.
3044
func NewCredentialDataSource() datasource.DataSource {
3145
return &credentialDataSource{}
@@ -127,7 +141,7 @@ func (r *credentialDataSource) Schema(_ context.Context, _ datasource.SchemaRequ
127141

128142
// Read refreshes the Terraform state with the latest data.
129143
func (r *credentialDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
130-
var model Model
144+
var model DataSourceModel
131145
diags := req.Config.Get(ctx, &model)
132146
resp.Diagnostics.Append(diags...)
133147
if resp.Diagnostics.HasError() {
@@ -162,7 +176,7 @@ func (r *credentialDataSource) Read(ctx context.Context, req datasource.ReadRequ
162176
ctx = core.LogResponse(ctx)
163177

164178
// Map response body to schema
165-
err = mapFields(ctx, recordSetResp, &model)
179+
err = mapDataSourceFields(ctx, recordSetResp, &model)
166180
if err != nil {
167181
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading credential", fmt.Sprintf("Processing API payload: %v", err))
168182
return
@@ -176,3 +190,60 @@ func (r *credentialDataSource) Read(ctx context.Context, req datasource.ReadRequ
176190
}
177191
tflog.Info(ctx, "mariadb credential read")
178192
}
193+
194+
func mapDataSourceFields(ctx context.Context, credentialsResp *mariadb.CredentialsResponse, model *DataSourceModel) error {
195+
if credentialsResp == nil {
196+
return fmt.Errorf("response input is nil")
197+
}
198+
if credentialsResp.Raw == nil {
199+
return fmt.Errorf("response credentials raw is nil")
200+
}
201+
if model == nil {
202+
return fmt.Errorf("model input is nil")
203+
}
204+
credentials := credentialsResp.Raw.Credentials
205+
206+
var credentialId string
207+
if model.CredentialId.ValueString() != "" {
208+
credentialId = model.CredentialId.ValueString()
209+
} else if credentialsResp.Id != "" {
210+
credentialId = credentialsResp.Id
211+
} else {
212+
return fmt.Errorf("credentials id not present")
213+
}
214+
215+
model.Id = utils.BuildInternalTerraformId(
216+
model.ProjectId.ValueString(),
217+
model.InstanceId.ValueString(),
218+
credentialId,
219+
)
220+
221+
modelHosts, err := utils.ListValueToStringSlice(model.Hosts)
222+
if err != nil {
223+
return err
224+
}
225+
226+
model.Hosts = types.ListNull(types.StringType)
227+
model.CredentialId = types.StringValue(credentialId)
228+
229+
if credentials.Hosts != nil {
230+
respHosts := credentials.Hosts
231+
232+
reconciledHosts := utils.ReconcileStringSlices(modelHosts, respHosts)
233+
234+
hostsTF, diags := types.ListValueFrom(ctx, types.StringType, reconciledHosts)
235+
if diags.HasError() {
236+
return fmt.Errorf("failed to map hosts: %w", core.DiagsToError(diags))
237+
}
238+
239+
model.Hosts = hostsTF
240+
}
241+
model.Host = types.StringValue(credentials.Host)
242+
model.Name = types.StringPointerValue(credentials.Name)
243+
model.Password = types.StringValue(credentials.Password)
244+
model.Port = types.Int32PointerValue(credentials.Port)
245+
model.Uri = types.StringPointerValue(credentials.Uri)
246+
model.Username = types.StringValue(credentials.Username)
247+
248+
return nil
249+
}
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
package mariadb
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/google/go-cmp/cmp"
8+
"github.com/hashicorp/terraform-plugin-framework/attr"
9+
"github.com/hashicorp/terraform-plugin-framework/types"
10+
mariadb "github.com/stackitcloud/stackit-sdk-go/services/mariadb/v1api"
11+
)
12+
13+
func TestMapDataSourceFields(t *testing.T) {
14+
tests := []struct {
15+
description string
16+
state DataSourceModel
17+
input *mariadb.CredentialsResponse
18+
expected DataSourceModel
19+
isValid bool
20+
}{
21+
{
22+
"default_values",
23+
DataSourceModel{
24+
InstanceId: types.StringValue("iid"),
25+
ProjectId: types.StringValue("pid"),
26+
},
27+
&mariadb.CredentialsResponse{
28+
Id: "cid",
29+
Raw: &mariadb.RawCredentials{},
30+
},
31+
DataSourceModel{
32+
Id: types.StringValue("pid,iid,cid"),
33+
CredentialId: types.StringValue("cid"),
34+
InstanceId: types.StringValue("iid"),
35+
ProjectId: types.StringValue("pid"),
36+
Host: types.StringValue(""),
37+
Hosts: types.ListNull(types.StringType),
38+
Name: types.StringNull(),
39+
Password: types.StringValue(""),
40+
Port: types.Int32Null(),
41+
Uri: types.StringNull(),
42+
Username: types.StringValue(""),
43+
},
44+
true,
45+
},
46+
{
47+
"simple_values",
48+
DataSourceModel{
49+
InstanceId: types.StringValue("iid"),
50+
ProjectId: types.StringValue("pid"),
51+
},
52+
&mariadb.CredentialsResponse{
53+
Id: "cid",
54+
Raw: &mariadb.RawCredentials{
55+
Credentials: mariadb.Credentials{
56+
Host: "host",
57+
Hosts: []string{
58+
"host_1",
59+
"",
60+
},
61+
Name: new("name"),
62+
Password: "password",
63+
Port: new(int32(1234)),
64+
Uri: new("uri"),
65+
Username: "username",
66+
},
67+
},
68+
},
69+
DataSourceModel{
70+
Id: types.StringValue("pid,iid,cid"),
71+
CredentialId: types.StringValue("cid"),
72+
InstanceId: types.StringValue("iid"),
73+
ProjectId: types.StringValue("pid"),
74+
Host: types.StringValue("host"),
75+
Hosts: types.ListValueMust(types.StringType, []attr.Value{
76+
types.StringValue("host_1"),
77+
types.StringValue(""),
78+
}),
79+
Name: types.StringValue("name"),
80+
Password: types.StringValue("password"),
81+
Port: types.Int32Value(1234),
82+
Uri: types.StringValue("uri"),
83+
Username: types.StringValue("username"),
84+
},
85+
true,
86+
},
87+
{
88+
"hosts_unordered",
89+
DataSourceModel{
90+
InstanceId: types.StringValue("iid"),
91+
ProjectId: types.StringValue("pid"),
92+
Hosts: types.ListValueMust(types.StringType, []attr.Value{
93+
types.StringValue("host_2"),
94+
types.StringValue(""),
95+
types.StringValue("host_1"),
96+
}),
97+
},
98+
&mariadb.CredentialsResponse{
99+
Id: "cid",
100+
Raw: &mariadb.RawCredentials{
101+
Credentials: mariadb.Credentials{
102+
Host: "host",
103+
Hosts: []string{
104+
"",
105+
"host_1",
106+
"host_2",
107+
},
108+
Name: new("name"),
109+
Password: "password",
110+
Port: new(int32(1234)),
111+
Uri: new("uri"),
112+
Username: "username",
113+
},
114+
},
115+
},
116+
DataSourceModel{
117+
Id: types.StringValue("pid,iid,cid"),
118+
CredentialId: types.StringValue("cid"),
119+
InstanceId: types.StringValue("iid"),
120+
ProjectId: types.StringValue("pid"),
121+
Host: types.StringValue("host"),
122+
Hosts: types.ListValueMust(types.StringType, []attr.Value{
123+
types.StringValue("host_2"),
124+
types.StringValue(""),
125+
types.StringValue("host_1"),
126+
}),
127+
Name: types.StringValue("name"),
128+
Password: types.StringValue("password"),
129+
Port: types.Int32Value(1234),
130+
Uri: types.StringValue("uri"),
131+
Username: types.StringValue("username"),
132+
},
133+
true,
134+
},
135+
{
136+
"null_fields_and_int_conversions",
137+
DataSourceModel{
138+
InstanceId: types.StringValue("iid"),
139+
ProjectId: types.StringValue("pid"),
140+
},
141+
&mariadb.CredentialsResponse{
142+
Id: "cid",
143+
Raw: &mariadb.RawCredentials{
144+
Credentials: mariadb.Credentials{
145+
Host: "",
146+
Hosts: []string{},
147+
Name: nil,
148+
Password: "",
149+
Port: new(int32(2123456789)),
150+
Uri: nil,
151+
Username: "",
152+
},
153+
},
154+
},
155+
DataSourceModel{
156+
Id: types.StringValue("pid,iid,cid"),
157+
CredentialId: types.StringValue("cid"),
158+
InstanceId: types.StringValue("iid"),
159+
ProjectId: types.StringValue("pid"),
160+
Host: types.StringValue(""),
161+
Hosts: types.ListValueMust(types.StringType, []attr.Value{}),
162+
Name: types.StringNull(),
163+
Password: types.StringValue(""),
164+
Port: types.Int32Value(2123456789),
165+
Uri: types.StringNull(),
166+
Username: types.StringValue(""),
167+
},
168+
true,
169+
},
170+
{
171+
"nil_response",
172+
DataSourceModel{
173+
InstanceId: types.StringValue("iid"),
174+
ProjectId: types.StringValue("pid"),
175+
},
176+
nil,
177+
DataSourceModel{},
178+
false,
179+
},
180+
{
181+
"no_resource_id",
182+
DataSourceModel{
183+
InstanceId: types.StringValue("iid"),
184+
ProjectId: types.StringValue("pid"),
185+
},
186+
&mariadb.CredentialsResponse{},
187+
DataSourceModel{},
188+
false,
189+
},
190+
{
191+
"nil_raw_credential",
192+
DataSourceModel{
193+
InstanceId: types.StringValue("iid"),
194+
ProjectId: types.StringValue("pid"),
195+
},
196+
&mariadb.CredentialsResponse{
197+
Id: "cid",
198+
},
199+
DataSourceModel{},
200+
false,
201+
},
202+
}
203+
for _, tt := range tests {
204+
t.Run(tt.description, func(t *testing.T) {
205+
err := mapDataSourceFields(context.Background(), tt.input, &tt.state)
206+
if !tt.isValid && err == nil {
207+
t.Fatalf("Should have failed")
208+
}
209+
if tt.isValid && err != nil {
210+
t.Fatalf("Should not have failed: %v", err)
211+
}
212+
if tt.isValid {
213+
diff := cmp.Diff(tt.state, tt.expected)
214+
if diff != "" {
215+
t.Fatalf("Data does not match: %s", diff)
216+
}
217+
}
218+
})
219+
}
220+
}

0 commit comments

Comments
 (0)