@@ -19,13 +19,14 @@ import (
19
19
20
20
// Ensure the implementation satisfies the expected interfaces.
21
21
var (
22
- _ resource.ResourceWithConfigure = (* projectResource )(nil )
23
- _ resource.ResourceWithImportState = (* projectResource )(nil )
22
+ _ resource.ResourceWithConfigure = (* projectResource )(nil )
23
+ _ resource.ResourceWithImportState = (* projectResource )(nil )
24
+ _ resource.ResourceWithUpgradeState = (* projectResource )(nil )
24
25
)
25
26
26
27
var (
27
28
resourceProjectName = "grafana_k6_project"
28
- resourceProjectID = common .NewResourceID (common .IntIDField ("id" ))
29
+ resourceProjectID = common .NewResourceID (common .StringIDField ("id" ))
29
30
)
30
31
31
32
func resourceProject () * common.Resource {
@@ -38,7 +39,7 @@ func resourceProject() *common.Resource {
38
39
}
39
40
40
41
// projectResourceModel maps the resource schema data.
41
- type projectResourceModel struct {
42
+ type projectResourceModelV0 struct {
42
43
ID types.Int32 `tfsdk:"id"`
43
44
Name types.String `tfsdk:"name"`
44
45
IsDefault types.Bool `tfsdk:"is_default"`
@@ -47,6 +48,15 @@ type projectResourceModel struct {
47
48
Updated types.String `tfsdk:"updated"`
48
49
}
49
50
51
+ type projectResourceModelV1 struct {
52
+ ID types.String `tfsdk:"id"`
53
+ Name types.String `tfsdk:"name"`
54
+ IsDefault types.Bool `tfsdk:"is_default"`
55
+ GrafanaFolderUID types.String `tfsdk:"grafana_folder_uid"`
56
+ Created types.String `tfsdk:"created"`
57
+ Updated types.String `tfsdk:"updated"`
58
+ }
59
+
50
60
// projectResource is the resource implementation.
51
61
type projectResource struct {
52
62
basePluginFrameworkResource
@@ -62,7 +72,7 @@ func (r *projectResource) Schema(_ context.Context, _ resource.SchemaRequest, re
62
72
resp .Schema = schema.Schema {
63
73
Description : "Manages a k6 project." ,
64
74
Attributes : map [string ]schema.Attribute {
65
- "id" : schema.Int32Attribute {
75
+ "id" : schema.StringAttribute {
66
76
Description : "Numeric identifier of the project." ,
67
77
Computed : true ,
68
78
},
@@ -87,13 +97,64 @@ func (r *projectResource) Schema(_ context.Context, _ resource.SchemaRequest, re
87
97
Computed : true ,
88
98
},
89
99
},
100
+ Version : 1 ,
101
+ }
102
+ }
103
+
104
+ func (r * projectResource ) UpgradeState (ctx context.Context ) map [int64 ]resource.StateUpgrader {
105
+ return map [int64 ]resource.StateUpgrader {
106
+ 0 : {
107
+ PriorSchema : & schema.Schema {
108
+ Attributes : map [string ]schema.Attribute {
109
+ "id" : schema.Int32Attribute {
110
+ Computed : true ,
111
+ },
112
+ "name" : schema.StringAttribute {
113
+ Required : true ,
114
+ },
115
+ "is_default" : schema.BoolAttribute {
116
+ Computed : true ,
117
+ },
118
+ "grafana_folder_uid" : schema.StringAttribute {
119
+ Computed : true ,
120
+ },
121
+ "created" : schema.StringAttribute {
122
+ Computed : true ,
123
+ },
124
+ "updated" : schema.StringAttribute {
125
+ Computed : true ,
126
+ },
127
+ },
128
+ },
129
+ StateUpgrader : func (ctx context.Context , req resource.UpgradeStateRequest , resp * resource.UpgradeStateResponse ) {
130
+ // Convert int32 ID to string ID
131
+ var priorStateData projectResourceModelV0
132
+ diags := req .State .Get (ctx , & priorStateData )
133
+ resp .Diagnostics .Append (diags ... )
134
+ if resp .Diagnostics .HasError () {
135
+ return
136
+ }
137
+
138
+ upgradedStateData := projectResourceModelV1 {
139
+ ID : types .StringValue (strconv .Itoa (int (priorStateData .ID .ValueInt32 ()))),
140
+ Name : priorStateData .Name ,
141
+ IsDefault : priorStateData .IsDefault ,
142
+ GrafanaFolderUID : priorStateData .GrafanaFolderUID ,
143
+ Created : priorStateData .Created ,
144
+ Updated : priorStateData .Updated ,
145
+ }
146
+
147
+ diags = resp .State .Set (ctx , upgradedStateData )
148
+ resp .Diagnostics .Append (diags ... )
149
+ },
150
+ },
90
151
}
91
152
}
92
153
93
154
// Create creates the resource and sets the Terraform state on success.
94
155
func (r * projectResource ) Create (ctx context.Context , req resource.CreateRequest , resp * resource.CreateResponse ) {
95
156
// Retrieve values from plan
96
- var plan projectResourceModel
157
+ var plan projectResourceModelV1
97
158
diags := req .Plan .Get (ctx , & plan )
98
159
resp .Diagnostics .Append (diags ... )
99
160
if resp .Diagnostics .HasError () {
@@ -119,7 +180,7 @@ func (r *projectResource) Create(ctx context.Context, req resource.CreateRequest
119
180
}
120
181
121
182
// Map response body to schema and populate Computed attribute values
122
- plan .ID = types .Int32Value ( p .GetId ())
183
+ plan .ID = types .StringValue ( strconv . Itoa ( int ( p .GetId ()) ))
123
184
plan .Name = types .StringValue (p .GetName ())
124
185
plan .IsDefault = types .BoolValue (p .GetIsDefault ())
125
186
plan .GrafanaFolderUID = handleGrafanaFolderUID (p .GrafanaFolderUid )
@@ -137,15 +198,31 @@ func (r *projectResource) Create(ctx context.Context, req resource.CreateRequest
137
198
// Read retrieves the resource information.
138
199
func (r * projectResource ) Read (ctx context.Context , req resource.ReadRequest , resp * resource.ReadResponse ) {
139
200
// Get current state
140
- var state projectResourceModel
201
+ var state projectResourceModelV1
141
202
diags := req .State .Get (ctx , & state )
142
203
resp .Diagnostics .Append (diags ... )
143
204
if resp .Diagnostics .HasError () {
144
205
return
145
206
}
146
207
208
+ // If the ID is empty, we cannot read the resource.
209
+ // This is required for crossplane to work, but it never happens in Terraform in practice.
210
+ if state .ID .IsNull () || state .ID .IsUnknown () || state .ID .ValueString () == "" {
211
+ resp .State .RemoveResource (ctx )
212
+ return
213
+ }
214
+
147
215
ctx = context .WithValue (ctx , k6 .ContextAccessToken , r .config .Token )
148
- k6Req := r .client .ProjectsAPI .ProjectsRetrieve (ctx , state .ID .ValueInt32 ()).
216
+ projectID , err := strconv .ParseInt (state .ID .ValueString (), 10 , 32 )
217
+ if err != nil {
218
+ resp .Diagnostics .AddError (
219
+ "Error parsing project ID" ,
220
+ "Could not parse project ID '" + state .ID .ValueString ()+ "': " + err .Error (),
221
+ )
222
+ return
223
+ }
224
+
225
+ k6Req := r .client .ProjectsAPI .ProjectsRetrieve (ctx , int32 (projectID )).
149
226
XStackId (r .config .StackID )
150
227
151
228
p , httpResp , err := k6Req .Execute ()
@@ -158,13 +235,13 @@ func (r *projectResource) Read(ctx context.Context, req resource.ReadRequest, re
158
235
if err != nil {
159
236
resp .Diagnostics .AddError (
160
237
"Error reading k6 project" ,
161
- "Could not read k6 project with id " + strconv . Itoa ( int ( state .ID .ValueInt32 ()) )+ ": " + err .Error (),
238
+ "Could not read k6 project with id " + state .ID .ValueString ( )+ ": " + err .Error (),
162
239
)
163
240
return
164
241
}
165
242
166
243
// Overwrite items with refreshed state
167
- state .ID = types .Int32Value ( p .GetId ())
244
+ state .ID = types .StringValue ( strconv . Itoa ( int ( p .GetId ()) ))
168
245
state .Name = types .StringValue (p .GetName ())
169
246
state .IsDefault = types .BoolValue (p .GetIsDefault ())
170
247
state .GrafanaFolderUID = handleGrafanaFolderUID (p .GrafanaFolderUid )
@@ -182,54 +259,64 @@ func (r *projectResource) Read(ctx context.Context, req resource.ReadRequest, re
182
259
// Update updates the resource and sets the updated Terraform state on success.
183
260
func (r * projectResource ) Update (ctx context.Context , req resource.UpdateRequest , resp * resource.UpdateResponse ) {
184
261
// Retrieve values from plan
185
- var plan projectResourceModel
262
+ var plan projectResourceModelV1
186
263
diags := req .Plan .Get (ctx , & plan )
187
264
resp .Diagnostics .Append (diags ... )
188
265
if resp .Diagnostics .HasError () {
189
266
return
190
267
}
191
268
192
269
// Get current state to retrieve the ID
193
- var state projectResourceModel
270
+ var state projectResourceModelV1
194
271
diags = req .State .Get (ctx , & state )
195
272
resp .Diagnostics .Append (diags ... )
196
273
if resp .Diagnostics .HasError () {
197
274
return
198
275
}
199
276
277
+ intID , err := strconv .ParseInt (state .ID .ValueString (), 10 , 32 )
278
+ if err != nil {
279
+ resp .Diagnostics .AddError (
280
+ "Error parsing project ID" ,
281
+ "Could not parse project ID '" + state .ID .ValueString ()+ "': " + err .Error (),
282
+ )
283
+ return
284
+ }
285
+ projectID := int32 (intID )
286
+
200
287
// Generate API request body from plan
201
288
toUpdate := k6 .NewPatchProjectApiModel (plan .Name .ValueString ())
202
289
203
290
ctx = context .WithValue (ctx , k6 .ContextAccessToken , r .config .Token )
204
- updateReq := r .client .ProjectsAPI .ProjectsPartialUpdate (ctx , state . ID . ValueInt32 () ).
291
+ updateReq := r .client .ProjectsAPI .ProjectsPartialUpdate (ctx , projectID ).
205
292
PatchProjectApiModel (toUpdate ).
206
293
XStackId (r .config .StackID )
207
294
208
295
// Update the project
209
- _ , err : = updateReq .Execute ()
296
+ _ , err = updateReq .Execute ()
210
297
if err != nil {
211
298
resp .Diagnostics .AddError (
212
299
"Error updating k6 project" ,
213
- "Could not update k6 project with id " + strconv . Itoa ( int ( state .ID .ValueInt32 ()) )+ ": " + err .Error (),
300
+ "Could not update k6 project with id " + state .ID .ValueString ( )+ ": " + err .Error (),
214
301
)
215
302
return
216
303
}
217
304
218
305
// Update resource state with updated items and timestamp
219
- fetchReq := r .client .ProjectsAPI .ProjectsRetrieve (ctx , state . ID . ValueInt32 () ).
306
+ fetchReq := r .client .ProjectsAPI .ProjectsRetrieve (ctx , projectID ).
220
307
XStackId (r .config .StackID )
221
308
222
309
p , _ , err := fetchReq .Execute ()
223
310
if err != nil {
224
311
resp .Diagnostics .AddError (
225
312
"Error reading k6 project" ,
226
- "Could not read k6 project with id " + strconv . Itoa ( int ( state .ID .ValueInt32 ()) )+ ": " + err .Error (),
313
+ "Could not read k6 project with id " + state .ID .ValueString ( )+ ": " + err .Error (),
227
314
)
228
315
return
229
316
}
230
317
231
318
// Overwrite items with refreshed state
232
- plan .ID = types .Int32Value ( p .GetId ())
319
+ plan .ID = types .StringValue ( strconv . Itoa ( int ( p .GetId ()) ))
233
320
plan .Name = types .StringValue (p .GetName ())
234
321
plan .IsDefault = types .BoolValue (p .GetIsDefault ())
235
322
plan .GrafanaFolderUID = handleGrafanaFolderUID (p .GrafanaFolderUid )
@@ -246,44 +333,39 @@ func (r *projectResource) Update(ctx context.Context, req resource.UpdateRequest
246
333
// Delete deletes the resource and removes the Terraform state on success.
247
334
func (r * projectResource ) Delete (ctx context.Context , req resource.DeleteRequest , resp * resource.DeleteResponse ) {
248
335
// Retrieve values from state
249
- var state projectResourceModel
336
+ var state projectResourceModelV1
250
337
diags := req .State .Get (ctx , & state )
251
338
resp .Diagnostics .Append (diags ... )
252
339
if resp .Diagnostics .HasError () {
253
340
return
254
341
}
255
342
343
+ intID , err := strconv .ParseInt (state .ID .ValueString (), 10 , 32 )
344
+ if err != nil {
345
+ resp .Diagnostics .AddError (
346
+ "Error parsing project ID" ,
347
+ "Could not parse project ID '" + state .ID .ValueString ()+ "': " + err .Error (),
348
+ )
349
+ return
350
+ }
351
+ projectID := int32 (intID )
352
+
256
353
// Delete existing project
257
354
ctx = context .WithValue (ctx , k6 .ContextAccessToken , r .config .Token )
258
- deleteReq := r .client .ProjectsAPI .ProjectsDestroy (ctx , state . ID . ValueInt32 () ).
355
+ deleteReq := r .client .ProjectsAPI .ProjectsDestroy (ctx , projectID ).
259
356
XStackId (r .config .StackID )
260
357
261
- _ , err : = deleteReq .Execute ()
358
+ _ , err = deleteReq .Execute ()
262
359
if err != nil {
263
360
resp .Diagnostics .AddError (
264
361
"Error deleting k6 project" ,
265
- "Could not delete k6 project with id " + strconv . Itoa ( int ( state .ID .ValueInt32 ()) )+ ": " + err .Error (),
362
+ "Could not delete k6 project with id " + state .ID .ValueString ( )+ ": " + err .Error (),
266
363
)
267
364
}
268
365
}
269
366
270
367
func (r * projectResource ) ImportState (ctx context.Context , req resource.ImportStateRequest , resp * resource.ImportStateResponse ) {
271
- id , err := strconv .ParseInt (req .ID , 10 , 32 )
272
- if err != nil {
273
- resp .Diagnostics .AddError (
274
- "Error importing k6 project" ,
275
- "Could not parse k6 project id " + req .ID + ": " + err .Error (),
276
- )
277
- return
278
- }
279
-
280
- resp .State .SetAttribute (ctx , path .Root ("id" ), types .Int32Value (int32 (id )))
281
-
282
- readReq := resource.ReadRequest {State : resp .State }
283
- readResp := resource.ReadResponse {State : resp .State }
284
-
285
- r .Read (ctx , readReq , & readResp )
286
- resp .Diagnostics .Append (readResp .Diagnostics ... )
368
+ resource .ImportStatePassthroughID (ctx , path .Root ("id" ), req , resp )
287
369
}
288
370
289
371
// listProjects retrieves the list ids of all the existing projects.
0 commit comments