Skip to content

Commit f5f99d1

Browse files
authored
fix(objectstorage): Removed unused attributes from datasource (#744)
Signed-off-by: Alexander Dahmen <alexander.dahmen@inovex.de>
1 parent a870b71 commit f5f99d1

4 files changed

Lines changed: 214 additions & 16 deletions

File tree

docs/data-sources/objectstorage_credential.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ ObjectStorage credential data source schema. Must have a `region` specified in t
1313
## Example Usage
1414

1515
```terraform
16-
data "stackit_objectstorage_credentials_group" "example" {
16+
data "stackit_objectstorage_credential" "example" {
1717
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
1818
credentials_group_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
1919
credential_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
@@ -35,8 +35,6 @@ data "stackit_objectstorage_credentials_group" "example" {
3535

3636
### Read-Only
3737

38-
- `access_key` (String)
3938
- `expiration_timestamp` (String)
4039
- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`credentials_group_id`,`credential_id`".
4140
- `name` (String)
42-
- `secret_access_key` (String, Sensitive)

examples/data-sources/stackit_objectstorage_credential/data-source.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
data "stackit_objectstorage_credentials_group" "example" {
1+
data "stackit_objectstorage_credential" "example" {
22
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
33
credentials_group_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
44
credential_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

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

Lines changed: 90 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,19 @@ package objectstorage
33
import (
44
"context"
55
"fmt"
6+
"net/http"
7+
"strings"
8+
"time"
69

710
"github.com/hashicorp/terraform-plugin-framework/datasource"
11+
"github.com/hashicorp/terraform-plugin-framework/types"
812
"github.com/hashicorp/terraform-plugin-log/tflog"
913
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
1014
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
1115

1216
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
1317
"github.com/stackitcloud/stackit-sdk-go/core/config"
18+
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
1419
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
1520
)
1621

@@ -19,6 +24,16 @@ var (
1924
_ datasource.DataSource = &credentialDataSource{}
2025
)
2126

27+
type DataSourceModel struct {
28+
Id types.String `tfsdk:"id"` // needed by TF
29+
CredentialId types.String `tfsdk:"credential_id"`
30+
CredentialsGroupId types.String `tfsdk:"credentials_group_id"`
31+
ProjectId types.String `tfsdk:"project_id"`
32+
Name types.String `tfsdk:"name"`
33+
ExpirationTimestamp types.String `tfsdk:"expiration_timestamp"`
34+
Region types.String `tfsdk:"region"`
35+
}
36+
2237
// NewCredentialDataSource is a helper function to simplify the provider implementation.
2338
func NewCredentialDataSource() datasource.DataSource {
2439
return &credentialDataSource{}
@@ -104,13 +119,6 @@ func (r *credentialDataSource) Schema(_ context.Context, _ datasource.SchemaRequ
104119
"name": schema.StringAttribute{
105120
Computed: true,
106121
},
107-
"access_key": schema.StringAttribute{
108-
Computed: true,
109-
},
110-
"secret_access_key": schema.StringAttribute{
111-
Computed: true,
112-
Sensitive: true,
113-
},
114122
"expiration_timestamp": schema.StringAttribute{
115123
Computed: true,
116124
},
@@ -125,7 +133,7 @@ func (r *credentialDataSource) Schema(_ context.Context, _ datasource.SchemaRequ
125133

126134
// Read refreshes the Terraform state with the latest data.
127135
func (r *credentialDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
128-
var model Model
136+
var model DataSourceModel
129137
diags := req.Config.Get(ctx, &model)
130138
resp.Diagnostics.Append(diags...)
131139
if resp.Diagnostics.HasError() {
@@ -147,17 +155,33 @@ func (r *credentialDataSource) Read(ctx context.Context, req datasource.ReadRequ
147155
ctx = tflog.SetField(ctx, "credential_id", credentialId)
148156
ctx = tflog.SetField(ctx, "region", region)
149157

150-
found, err := readCredentials(ctx, &model, region, r.client)
158+
credentialsGroupResp, err := r.client.ListAccessKeys(ctx, projectId, region).CredentialsGroup(credentialsGroupId).Execute()
151159
if err != nil {
152-
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading credential", fmt.Sprintf("Finding credential: %v", err))
160+
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
161+
if ok && oapiErr.StatusCode == http.StatusNotFound {
162+
resp.State.RemoveResource(ctx)
163+
return
164+
}
165+
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading credentials", fmt.Sprintf("Calling API: %v", err))
166+
return
167+
}
168+
if credentialsGroupResp == nil {
169+
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading credentials", fmt.Sprintf("Response is nil: %v", err))
153170
return
154171
}
155-
if !found {
156-
resp.State.RemoveResource(ctx)
172+
173+
credential := findCredential(*credentialsGroupResp, credentialId)
174+
if credential == nil {
157175
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading credential", "Credential not found")
158176
return
159177
}
160178

179+
err = mapDataSourceFields(credential, &model, region)
180+
if err != nil {
181+
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading credential", fmt.Sprintf("Processing API payload: %v", err))
182+
return
183+
}
184+
161185
// Set refreshed state
162186
diags = resp.State.Set(ctx, model)
163187
resp.Diagnostics.Append(diags...)
@@ -166,3 +190,57 @@ func (r *credentialDataSource) Read(ctx context.Context, req datasource.ReadRequ
166190
}
167191
tflog.Info(ctx, "ObjectStorage credential read")
168192
}
193+
194+
func mapDataSourceFields(credentialResp *objectstorage.AccessKey, model *DataSourceModel, region string) error {
195+
if credentialResp == nil {
196+
return fmt.Errorf("response input is nil")
197+
}
198+
if model == nil {
199+
return fmt.Errorf("model input is nil")
200+
}
201+
202+
var credentialId string
203+
if model.CredentialId.ValueString() != "" {
204+
credentialId = model.CredentialId.ValueString()
205+
} else if credentialResp.KeyId != nil {
206+
credentialId = *credentialResp.KeyId
207+
} else {
208+
return fmt.Errorf("credential id not present")
209+
}
210+
211+
if credentialResp.Expires == nil {
212+
model.ExpirationTimestamp = types.StringNull()
213+
} else {
214+
// Harmonize the timestamp format
215+
// Eg. "2027-01-02T03:04:05.000Z" = "2027-01-02T03:04:05Z"
216+
expirationTimestamp, err := time.Parse(time.RFC3339, *credentialResp.Expires)
217+
if err != nil {
218+
return fmt.Errorf("unable to parse payload expiration timestamp '%v': %w", *credentialResp.Expires, err)
219+
}
220+
model.ExpirationTimestamp = types.StringValue(expirationTimestamp.Format(time.RFC3339))
221+
}
222+
223+
idParts := []string{
224+
model.ProjectId.ValueString(),
225+
model.CredentialsGroupId.ValueString(),
226+
credentialId,
227+
}
228+
model.Id = types.StringValue(
229+
strings.Join(idParts, core.Separator),
230+
)
231+
model.CredentialId = types.StringValue(credentialId)
232+
model.Name = types.StringPointerValue(credentialResp.DisplayName)
233+
model.Region = types.StringValue(region)
234+
return nil
235+
}
236+
237+
// Returns the access key if found otherwise nil
238+
func findCredential(credentialsGroupResp objectstorage.ListAccessKeysResponse, credentialId string) *objectstorage.AccessKey {
239+
for _, credential := range *credentialsGroupResp.AccessKeys {
240+
if credential.KeyId == nil || *credential.KeyId != credentialId {
241+
continue
242+
}
243+
return &credential
244+
}
245+
return nil
246+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package objectstorage
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/google/go-cmp/cmp"
8+
"github.com/hashicorp/terraform-plugin-framework/types"
9+
"github.com/stackitcloud/stackit-sdk-go/core/utils"
10+
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
11+
)
12+
13+
func TestMapDatasourceFields(t *testing.T) {
14+
now := time.Now()
15+
16+
tests := []struct {
17+
description string
18+
input *objectstorage.AccessKey
19+
expected DataSourceModel
20+
isValid bool
21+
}{
22+
{
23+
"default_values",
24+
&objectstorage.AccessKey{},
25+
DataSourceModel{
26+
Id: types.StringValue("pid,cgid,cid"),
27+
ProjectId: types.StringValue("pid"),
28+
CredentialsGroupId: types.StringValue("cgid"),
29+
CredentialId: types.StringValue("cid"),
30+
Name: types.StringNull(),
31+
ExpirationTimestamp: types.StringNull(),
32+
Region: types.StringValue("eu01"),
33+
},
34+
true,
35+
},
36+
{
37+
"simple_values",
38+
&objectstorage.AccessKey{
39+
DisplayName: utils.Ptr("name"),
40+
Expires: utils.Ptr(now.Format(time.RFC3339)),
41+
},
42+
DataSourceModel{
43+
Id: types.StringValue("pid,cgid,cid"),
44+
ProjectId: types.StringValue("pid"),
45+
CredentialsGroupId: types.StringValue("cgid"),
46+
CredentialId: types.StringValue("cid"),
47+
Name: types.StringValue("name"),
48+
ExpirationTimestamp: types.StringValue(now.Format(time.RFC3339)),
49+
Region: types.StringValue("eu01"),
50+
},
51+
true,
52+
},
53+
{
54+
"empty_strings",
55+
&objectstorage.AccessKey{
56+
DisplayName: utils.Ptr(""),
57+
},
58+
DataSourceModel{
59+
Id: types.StringValue("pid,cgid,cid"),
60+
ProjectId: types.StringValue("pid"),
61+
CredentialsGroupId: types.StringValue("cgid"),
62+
CredentialId: types.StringValue("cid"),
63+
Name: types.StringValue(""),
64+
ExpirationTimestamp: types.StringNull(),
65+
Region: types.StringValue("eu01"),
66+
},
67+
true,
68+
},
69+
{
70+
"expiration_timestamp_with_fractional_seconds",
71+
&objectstorage.AccessKey{
72+
Expires: utils.Ptr(now.Format(time.RFC3339Nano)),
73+
},
74+
DataSourceModel{
75+
Id: types.StringValue("pid,cgid,cid"),
76+
ProjectId: types.StringValue("pid"),
77+
CredentialsGroupId: types.StringValue("cgid"),
78+
CredentialId: types.StringValue("cid"),
79+
Name: types.StringNull(),
80+
ExpirationTimestamp: types.StringValue(now.Format(time.RFC3339)),
81+
Region: types.StringValue("eu01"),
82+
},
83+
true,
84+
},
85+
{
86+
"nil_response",
87+
nil,
88+
DataSourceModel{},
89+
false,
90+
},
91+
{
92+
"bad_time",
93+
&objectstorage.AccessKey{
94+
Expires: utils.Ptr("foo-bar"),
95+
},
96+
DataSourceModel{},
97+
false,
98+
},
99+
}
100+
for _, tt := range tests {
101+
t.Run(tt.description, func(t *testing.T) {
102+
model := &DataSourceModel{
103+
ProjectId: tt.expected.ProjectId,
104+
CredentialsGroupId: tt.expected.CredentialsGroupId,
105+
CredentialId: tt.expected.CredentialId,
106+
}
107+
err := mapDataSourceFields(tt.input, model, "eu01")
108+
if !tt.isValid && err == nil {
109+
t.Fatalf("Should have failed")
110+
}
111+
if tt.isValid && err != nil {
112+
t.Fatalf("Should not have failed: %v", err)
113+
}
114+
if tt.isValid {
115+
diff := cmp.Diff(model, &tt.expected)
116+
if diff != "" {
117+
t.Fatalf("Data does not match: %s", diff)
118+
}
119+
}
120+
})
121+
}
122+
}

0 commit comments

Comments
 (0)