fix(objectstorage): Removed unused attributes from datasource (#744)
Signed-off-by: Alexander Dahmen <alexander.dahmen@inovex.de>
This commit is contained in:
parent
a870b71d0a
commit
f5f99d1709
4 changed files with 214 additions and 16 deletions
|
|
@ -13,7 +13,7 @@ ObjectStorage credential data source schema. Must have a `region` specified in t
|
||||||
## Example Usage
|
## Example Usage
|
||||||
|
|
||||||
```terraform
|
```terraform
|
||||||
data "stackit_objectstorage_credentials_group" "example" {
|
data "stackit_objectstorage_credential" "example" {
|
||||||
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||||
credentials_group_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
credentials_group_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||||
credential_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
credential_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||||
|
|
@ -35,8 +35,6 @@ data "stackit_objectstorage_credentials_group" "example" {
|
||||||
|
|
||||||
### Read-Only
|
### Read-Only
|
||||||
|
|
||||||
- `access_key` (String)
|
|
||||||
- `expiration_timestamp` (String)
|
- `expiration_timestamp` (String)
|
||||||
- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`credentials_group_id`,`credential_id`".
|
- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`credentials_group_id`,`credential_id`".
|
||||||
- `name` (String)
|
- `name` (String)
|
||||||
- `secret_access_key` (String, Sensitive)
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
data "stackit_objectstorage_credentials_group" "example" {
|
data "stackit_objectstorage_credential" "example" {
|
||||||
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||||
credentials_group_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
credentials_group_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||||
credential_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
credential_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,19 @@ package objectstorage
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
|
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
|
||||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
|
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
||||||
"github.com/stackitcloud/stackit-sdk-go/core/config"
|
"github.com/stackitcloud/stackit-sdk-go/core/config"
|
||||||
|
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
|
||||||
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
|
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -19,6 +24,16 @@ var (
|
||||||
_ datasource.DataSource = &credentialDataSource{}
|
_ 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.
|
// NewCredentialDataSource is a helper function to simplify the provider implementation.
|
||||||
func NewCredentialDataSource() datasource.DataSource {
|
func NewCredentialDataSource() datasource.DataSource {
|
||||||
return &credentialDataSource{}
|
return &credentialDataSource{}
|
||||||
|
|
@ -104,13 +119,6 @@ func (r *credentialDataSource) Schema(_ context.Context, _ datasource.SchemaRequ
|
||||||
"name": schema.StringAttribute{
|
"name": schema.StringAttribute{
|
||||||
Computed: true,
|
Computed: true,
|
||||||
},
|
},
|
||||||
"access_key": schema.StringAttribute{
|
|
||||||
Computed: true,
|
|
||||||
},
|
|
||||||
"secret_access_key": schema.StringAttribute{
|
|
||||||
Computed: true,
|
|
||||||
Sensitive: true,
|
|
||||||
},
|
|
||||||
"expiration_timestamp": schema.StringAttribute{
|
"expiration_timestamp": schema.StringAttribute{
|
||||||
Computed: true,
|
Computed: true,
|
||||||
},
|
},
|
||||||
|
|
@ -125,7 +133,7 @@ func (r *credentialDataSource) Schema(_ context.Context, _ datasource.SchemaRequ
|
||||||
|
|
||||||
// Read refreshes the Terraform state with the latest data.
|
// 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
|
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)
|
diags := req.Config.Get(ctx, &model)
|
||||||
resp.Diagnostics.Append(diags...)
|
resp.Diagnostics.Append(diags...)
|
||||||
if resp.Diagnostics.HasError() {
|
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, "credential_id", credentialId)
|
||||||
ctx = tflog.SetField(ctx, "region", region)
|
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 {
|
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
|
return
|
||||||
}
|
}
|
||||||
if !found {
|
if credentialsGroupResp == nil {
|
||||||
resp.State.RemoveResource(ctx)
|
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")
|
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading credential", "Credential not found")
|
||||||
return
|
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
|
// Set refreshed state
|
||||||
diags = resp.State.Set(ctx, model)
|
diags = resp.State.Set(ctx, model)
|
||||||
resp.Diagnostics.Append(diags...)
|
resp.Diagnostics.Append(diags...)
|
||||||
|
|
@ -166,3 +190,57 @@ func (r *credentialDataSource) Read(ctx context.Context, req datasource.ReadRequ
|
||||||
}
|
}
|
||||||
tflog.Info(ctx, "ObjectStorage credential read")
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue