fix(objectstorage): Removed unused attributes from datasource (#744)

Signed-off-by: Alexander Dahmen <alexander.dahmen@inovex.de>
This commit is contained in:
Alexander Dahmen 2025-03-28 09:46:20 +01:00 committed by GitHub
parent a870b71d0a
commit f5f99d1709
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 214 additions and 16 deletions

View file

@ -3,14 +3,19 @@ package objectstorage
import (
"context"
"fmt"
"net/http"
"strings"
"time"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/stackitcloud/stackit-sdk-go/core/config"
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
)
@ -19,6 +24,16 @@ var (
_ datasource.DataSource = &credentialDataSource{}
)
type DataSourceModel struct {
Id types.String `tfsdk:"id"` // needed by TF
CredentialId types.String `tfsdk:"credential_id"`
CredentialsGroupId types.String `tfsdk:"credentials_group_id"`
ProjectId types.String `tfsdk:"project_id"`
Name types.String `tfsdk:"name"`
ExpirationTimestamp types.String `tfsdk:"expiration_timestamp"`
Region types.String `tfsdk:"region"`
}
// NewCredentialDataSource is a helper function to simplify the provider implementation.
func NewCredentialDataSource() datasource.DataSource {
return &credentialDataSource{}
@ -104,13 +119,6 @@ func (r *credentialDataSource) Schema(_ context.Context, _ datasource.SchemaRequ
"name": schema.StringAttribute{
Computed: true,
},
"access_key": schema.StringAttribute{
Computed: true,
},
"secret_access_key": schema.StringAttribute{
Computed: true,
Sensitive: true,
},
"expiration_timestamp": schema.StringAttribute{
Computed: true,
},
@ -125,7 +133,7 @@ func (r *credentialDataSource) Schema(_ context.Context, _ datasource.SchemaRequ
// Read refreshes the Terraform state with the latest data.
func (r *credentialDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
var model Model
var model DataSourceModel
diags := req.Config.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
@ -147,17 +155,33 @@ func (r *credentialDataSource) Read(ctx context.Context, req datasource.ReadRequ
ctx = tflog.SetField(ctx, "credential_id", credentialId)
ctx = tflog.SetField(ctx, "region", region)
found, err := readCredentials(ctx, &model, region, r.client)
credentialsGroupResp, err := r.client.ListAccessKeys(ctx, projectId, region).CredentialsGroup(credentialsGroupId).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading credential", fmt.Sprintf("Finding credential: %v", err))
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
if ok && oapiErr.StatusCode == http.StatusNotFound {
resp.State.RemoveResource(ctx)
return
}
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading credentials", fmt.Sprintf("Calling API: %v", err))
return
}
if !found {
resp.State.RemoveResource(ctx)
if credentialsGroupResp == nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading credentials", fmt.Sprintf("Response is nil: %v", err))
return
}
credential := findCredential(*credentialsGroupResp, credentialId)
if credential == nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading credential", "Credential not found")
return
}
err = mapDataSourceFields(credential, &model, region)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading credential", fmt.Sprintf("Processing API payload: %v", err))
return
}
// Set refreshed state
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
@ -166,3 +190,57 @@ func (r *credentialDataSource) Read(ctx context.Context, req datasource.ReadRequ
}
tflog.Info(ctx, "ObjectStorage credential read")
}
func mapDataSourceFields(credentialResp *objectstorage.AccessKey, model *DataSourceModel, region string) error {
if credentialResp == nil {
return fmt.Errorf("response input is nil")
}
if model == nil {
return fmt.Errorf("model input is nil")
}
var credentialId string
if model.CredentialId.ValueString() != "" {
credentialId = model.CredentialId.ValueString()
} else if credentialResp.KeyId != nil {
credentialId = *credentialResp.KeyId
} else {
return fmt.Errorf("credential id not present")
}
if credentialResp.Expires == nil {
model.ExpirationTimestamp = types.StringNull()
} else {
// Harmonize the timestamp format
// Eg. "2027-01-02T03:04:05.000Z" = "2027-01-02T03:04:05Z"
expirationTimestamp, err := time.Parse(time.RFC3339, *credentialResp.Expires)
if err != nil {
return fmt.Errorf("unable to parse payload expiration timestamp '%v': %w", *credentialResp.Expires, err)
}
model.ExpirationTimestamp = types.StringValue(expirationTimestamp.Format(time.RFC3339))
}
idParts := []string{
model.ProjectId.ValueString(),
model.CredentialsGroupId.ValueString(),
credentialId,
}
model.Id = types.StringValue(
strings.Join(idParts, core.Separator),
)
model.CredentialId = types.StringValue(credentialId)
model.Name = types.StringPointerValue(credentialResp.DisplayName)
model.Region = types.StringValue(region)
return nil
}
// Returns the access key if found otherwise nil
func findCredential(credentialsGroupResp objectstorage.ListAccessKeysResponse, credentialId string) *objectstorage.AccessKey {
for _, credential := range *credentialsGroupResp.AccessKeys {
if credential.KeyId == nil || *credential.KeyId != credentialId {
continue
}
return &credential
}
return nil
}

View file

@ -0,0 +1,122 @@
package objectstorage
import (
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/stackitcloud/stackit-sdk-go/core/utils"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
)
func TestMapDatasourceFields(t *testing.T) {
now := time.Now()
tests := []struct {
description string
input *objectstorage.AccessKey
expected DataSourceModel
isValid bool
}{
{
"default_values",
&objectstorage.AccessKey{},
DataSourceModel{
Id: types.StringValue("pid,cgid,cid"),
ProjectId: types.StringValue("pid"),
CredentialsGroupId: types.StringValue("cgid"),
CredentialId: types.StringValue("cid"),
Name: types.StringNull(),
ExpirationTimestamp: types.StringNull(),
Region: types.StringValue("eu01"),
},
true,
},
{
"simple_values",
&objectstorage.AccessKey{
DisplayName: utils.Ptr("name"),
Expires: utils.Ptr(now.Format(time.RFC3339)),
},
DataSourceModel{
Id: types.StringValue("pid,cgid,cid"),
ProjectId: types.StringValue("pid"),
CredentialsGroupId: types.StringValue("cgid"),
CredentialId: types.StringValue("cid"),
Name: types.StringValue("name"),
ExpirationTimestamp: types.StringValue(now.Format(time.RFC3339)),
Region: types.StringValue("eu01"),
},
true,
},
{
"empty_strings",
&objectstorage.AccessKey{
DisplayName: utils.Ptr(""),
},
DataSourceModel{
Id: types.StringValue("pid,cgid,cid"),
ProjectId: types.StringValue("pid"),
CredentialsGroupId: types.StringValue("cgid"),
CredentialId: types.StringValue("cid"),
Name: types.StringValue(""),
ExpirationTimestamp: types.StringNull(),
Region: types.StringValue("eu01"),
},
true,
},
{
"expiration_timestamp_with_fractional_seconds",
&objectstorage.AccessKey{
Expires: utils.Ptr(now.Format(time.RFC3339Nano)),
},
DataSourceModel{
Id: types.StringValue("pid,cgid,cid"),
ProjectId: types.StringValue("pid"),
CredentialsGroupId: types.StringValue("cgid"),
CredentialId: types.StringValue("cid"),
Name: types.StringNull(),
ExpirationTimestamp: types.StringValue(now.Format(time.RFC3339)),
Region: types.StringValue("eu01"),
},
true,
},
{
"nil_response",
nil,
DataSourceModel{},
false,
},
{
"bad_time",
&objectstorage.AccessKey{
Expires: utils.Ptr("foo-bar"),
},
DataSourceModel{},
false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
model := &DataSourceModel{
ProjectId: tt.expected.ProjectId,
CredentialsGroupId: tt.expected.CredentialsGroupId,
CredentialId: tt.expected.CredentialId,
}
err := mapDataSourceFields(tt.input, model, "eu01")
if !tt.isValid && err == nil {
t.Fatalf("Should have failed")
}
if tt.isValid && err != nil {
t.Fatalf("Should not have failed: %v", err)
}
if tt.isValid {
diff := cmp.Diff(model, &tt.expected)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
}
})
}
}