@@ -4,14 +4,13 @@ import (
4
4
"context"
5
5
"fmt"
6
6
"strings"
7
+ "time"
7
8
8
- "github.com/hashicorp/terraform-plugin-framework/diag"
9
9
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
10
10
"github.com/hashicorp/terraform-plugin-log/tflog"
11
11
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
12
12
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
13
13
14
- "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes"
15
14
"github.com/hashicorp/terraform-plugin-framework/path"
16
15
"github.com/hashicorp/terraform-plugin-framework/resource"
17
16
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
@@ -30,14 +29,14 @@ var (
30
29
)
31
30
32
31
type Model struct {
33
- Id types.String `tfsdk:"id"` // needed by TF
34
- CredentialId types.String `tfsdk:"credential_id"`
35
- CredentialsGroupId types.String `tfsdk:"credentials_group_id"`
36
- ProjectId types.String `tfsdk:"project_id"`
37
- Name types.String `tfsdk:"name"`
38
- AccessKey types.String `tfsdk:"access_key"`
39
- SecretAccessKey types.String `tfsdk:"secret_access_key"`
40
- ExpirationTimestamp timetypes. RFC3339 `tfsdk:"expiration_timestamp"`
32
+ Id types.String `tfsdk:"id"` // needed by TF
33
+ CredentialId types.String `tfsdk:"credential_id"`
34
+ CredentialsGroupId types.String `tfsdk:"credentials_group_id"`
35
+ ProjectId types.String `tfsdk:"project_id"`
36
+ Name types.String `tfsdk:"name"`
37
+ AccessKey types.String `tfsdk:"access_key"`
38
+ SecretAccessKey types.String `tfsdk:"secret_access_key"`
39
+ ExpirationTimestamp types. String `tfsdk:"expiration_timestamp"`
41
40
}
42
41
43
42
// NewCredentialResource is a helper function to simplify the provider implementation.
@@ -99,6 +98,7 @@ func (r *credentialResource) Schema(_ context.Context, _ resource.SchemaRequest,
99
98
"credential_id" : "The credential ID." ,
100
99
"credentials_group_id" : "The credential group ID." ,
101
100
"project_id" : "STACKIT Project ID to which the credential group is associated." ,
101
+ "expiration_timestamp" : "Expiration timestamp, in RFC339 format without fractional seconds. Example: \" 2025-01-01T00:00:00Z\" . If not set, the credential never expires." ,
102
102
}
103
103
104
104
resp .Schema = schema.Schema {
@@ -124,7 +124,7 @@ func (r *credentialResource) Schema(_ context.Context, _ resource.SchemaRequest,
124
124
},
125
125
"credentials_group_id" : schema.StringAttribute {
126
126
Description : descriptions ["credentials_group_id" ],
127
- Computed : true ,
127
+ Required : true ,
128
128
PlanModifiers : []planmodifier.String {
129
129
stringplanmodifier .UseStateForUnknown (),
130
130
},
@@ -156,9 +156,12 @@ func (r *credentialResource) Schema(_ context.Context, _ resource.SchemaRequest,
156
156
Sensitive : true ,
157
157
},
158
158
"expiration_timestamp" : schema.StringAttribute {
159
- CustomType : timetypes.RFC3339Type {},
160
- Optional : true ,
161
- Computed : true ,
159
+ Description : descriptions ["expiration_timestamp" ],
160
+ Optional : true ,
161
+ Computed : true ,
162
+ Validators : []validator.String {
163
+ validate .RFC3339SecondsOnly (),
164
+ },
162
165
PlanModifiers : []planmodifier.String {
163
166
stringplanmodifier .UseStateForUnknown (),
164
167
},
@@ -325,9 +328,13 @@ func toCreatePayload(model *Model) (*objectstorage.CreateAccessKeyPayload, error
325
328
return & objectstorage.CreateAccessKeyPayload {}, nil
326
329
}
327
330
328
- expirationTimestamp , diags := model .ExpirationTimestamp .ValueRFC3339Time ()
329
- if diags .HasError () {
330
- return nil , fmt .Errorf ("unable to fecth expiration timestamp: %w" , core .DiagsToError (diags ))
331
+ expirationTimestampValue := model .ExpirationTimestamp .ValueStringPointer ()
332
+ if expirationTimestampValue == nil {
333
+ return & objectstorage.CreateAccessKeyPayload {}, nil
334
+ }
335
+ expirationTimestamp , err := time .Parse (time .RFC3339 , * expirationTimestampValue )
336
+ if err != nil {
337
+ return nil , fmt .Errorf ("unable to parse expiration timestamp '%v': %w" , * expirationTimestampValue , err )
331
338
}
332
339
return & objectstorage.CreateAccessKeyPayload {
333
340
Expires : & expirationTimestamp ,
@@ -351,7 +358,17 @@ func mapFields(credentialResp *objectstorage.CreateAccessKeyResponse, model *Mod
351
358
return fmt .Errorf ("credential id not present" )
352
359
}
353
360
354
- var diags diag.Diagnostics
361
+ if credentialResp .Expires == nil {
362
+ model .ExpirationTimestamp = types .StringNull ()
363
+ } else {
364
+ // Harmonize the timestamp format
365
+ // Eg. "2027-01-02T03:04:05.000Z" = "2027-01-02T03:04:05Z"
366
+ expirationTimestamp , err := time .Parse (time .RFC3339 , * credentialResp .Expires )
367
+ if err != nil {
368
+ return fmt .Errorf ("unable to parse payload expiration timestamp '%v': %w" , * credentialResp .Expires , err )
369
+ }
370
+ model .ExpirationTimestamp = types .StringValue (expirationTimestamp .Format (time .RFC3339 ))
371
+ }
355
372
356
373
idParts := []string {
357
374
model .ProjectId .ValueString (),
@@ -365,10 +382,6 @@ func mapFields(credentialResp *objectstorage.CreateAccessKeyResponse, model *Mod
365
382
model .Name = types .StringPointerValue (credentialResp .DisplayName )
366
383
model .AccessKey = types .StringPointerValue (credentialResp .AccessKey )
367
384
model .SecretAccessKey = types .StringPointerValue (credentialResp .SecretAccessKey )
368
- model .ExpirationTimestamp , diags = timetypes .NewRFC3339PointerValue (credentialResp .Expires )
369
- if diags .HasError () {
370
- return fmt .Errorf ("parsing expiration timestamp: %w" , core .DiagsToError (diags ))
371
- }
372
385
return nil
373
386
}
374
387
@@ -396,8 +409,6 @@ func readCredentials(ctx context.Context, model *Model, client *objectstorage.AP
396
409
397
410
foundCredential = true
398
411
399
- var diags diag.Diagnostics
400
-
401
412
idParts := []string {
402
413
projectId ,
403
414
credentialsGroupId ,
@@ -407,9 +418,17 @@ func readCredentials(ctx context.Context, model *Model, client *objectstorage.AP
407
418
strings .Join (idParts , core .Separator ),
408
419
)
409
420
model .Name = types .StringPointerValue (credential .DisplayName )
410
- model .ExpirationTimestamp , diags = timetypes .NewRFC3339PointerValue (credential .Expires )
411
- if diags .HasError () {
412
- return fmt .Errorf ("parsing expiration timestamp: %w" , core .DiagsToError (diags ))
421
+
422
+ if credential .Expires == nil {
423
+ model .ExpirationTimestamp = types .StringNull ()
424
+ } else {
425
+ // Harmonize the timestamp format
426
+ // Eg. "2027-01-02T03:04:05.000Z" = "2027-01-02T03:04:05Z"
427
+ expirationTimestamp , err := time .Parse (time .RFC3339 , * credential .Expires )
428
+ if err != nil {
429
+ return fmt .Errorf ("unable to parse payload expiration timestamp '%v': %w" , * credential .Expires , err )
430
+ }
431
+ model .ExpirationTimestamp = types .StringValue (expirationTimestamp .Format (time .RFC3339 ))
413
432
}
414
433
break
415
434
}
0 commit comments