Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/resources/objectstorage_credential.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,20 @@ resource "stackit_objectstorage_credential" "example" {
expiration_timestamp = "2027-01-02T03:04:05Z"
}

resource "time_rotating" "rotate" {
rotation_days = 80
}

resource "stackit_objectstorage_credential" "rotate_example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
credentials_group_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
expiration_timestamp = "2027-01-02T03:04:05Z"

rotate_when_changed = {
rotation = time_rotating.rotate.id
}
}

# Only use the import statement, if you want to import an existing objectstorage credential
import {
to = stackit_objectstorage_credential.import-example
Expand All @@ -38,6 +52,7 @@ import {

- `expiration_timestamp` (String) Expiration timestamp, in RFC339 format without fractional seconds. Example: "2025-01-01T00:00:00Z". If not set, the credential never expires.
- `region` (String) The resource region. If not defined, the provider region is used.
- `rotate_when_changed` (Map of String) A map of arbitrary key/value pairs that will force recreation of the resource when they change, enabling resource rotation based on external conditions such as a rotating timestamp. Changing this forces a new resource to be created.

### Read-Only

Expand Down
14 changes: 14 additions & 0 deletions examples/resources/stackit_objectstorage_credential/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@ resource "stackit_objectstorage_credential" "example" {
expiration_timestamp = "2027-01-02T03:04:05Z"
}

resource "time_rotating" "rotate" {
rotation_days = 80
}

resource "stackit_objectstorage_credential" "rotate_example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
credentials_group_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
expiration_timestamp = "2027-01-02T03:04:05Z"

rotate_when_changed = {
rotation = time_rotating.rotate.id
}
}

# Only use the import statement, if you want to import an existing objectstorage credential
import {
to = stackit_objectstorage_credential.import-example
Expand Down
19 changes: 19 additions & 0 deletions stackit/internal/services/objectstorage/credential/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"strings"
"time"

"github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier"

objectstorageUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/objectstorage/utils"

"github.com/hashicorp/terraform-plugin-framework/schema/validator"
Expand Down Expand Up @@ -46,6 +48,11 @@ type Model struct {
SecretAccessKey types.String `tfsdk:"secret_access_key"`
ExpirationTimestamp types.String `tfsdk:"expiration_timestamp"`
Region types.String `tfsdk:"region"`
// RotateWhenChanged is a map of arbitrary key/value pairs that will force
// recreation of the resource when they change, enabling resource rotation based on
// external conditions such as a rotating timestamp. Changing this forces a new
// resource to be created.
RotateWhenChanged types.Map `tfsdk:"rotate_when_changed"`
}

// NewCredentialResource is a helper function to simplify the provider implementation.
Expand Down Expand Up @@ -238,6 +245,18 @@ func (r *credentialResource) Schema(_ context.Context, _ resource.SchemaRequest,
stringplanmodifier.RequiresReplace(),
},
},
"rotate_when_changed": schema.MapAttribute{
Description: "A map of arbitrary key/value pairs that will force " +
"recreation of the resource when they change, enabling resource rotation " +
"based on external conditions such as a rotating timestamp. Changing " +
"this forces a new resource to be created.",
Optional: true,
Required: false,
ElementType: types.StringType,
PlanModifiers: []planmodifier.Map{
mapplanmodifier.RequiresReplace(),
},
},
},
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ func TestMapFields(t *testing.T) {
SecretAccessKey: types.StringValue(""),
ExpirationTimestamp: types.StringNull(),
Region: types.StringValue("eu01"),
RotateWhenChanged: types.MapNull(types.StringType),
},
true,
},
Expand All @@ -75,6 +76,7 @@ func TestMapFields(t *testing.T) {
SecretAccessKey: types.StringValue("secret-key"),
ExpirationTimestamp: types.StringValue(now.Format(time.RFC3339)),
Region: types.StringValue("eu01"),
RotateWhenChanged: types.MapNull(types.StringType),
},
true,
},
Expand All @@ -95,6 +97,7 @@ func TestMapFields(t *testing.T) {
SecretAccessKey: types.StringValue(""),
ExpirationTimestamp: types.StringNull(),
Region: types.StringValue("eu01"),
RotateWhenChanged: types.MapNull(types.StringType),
},
true,
},
Expand All @@ -113,6 +116,7 @@ func TestMapFields(t *testing.T) {
SecretAccessKey: types.StringValue(""),
ExpirationTimestamp: types.StringValue(now.Format(time.RFC3339)),
Region: types.StringValue("eu01"),
RotateWhenChanged: types.MapNull(types.StringType),
},
true,
},
Expand All @@ -137,6 +141,7 @@ func TestMapFields(t *testing.T) {
ProjectId: tt.expected.ProjectId,
CredentialsGroupId: tt.expected.CredentialsGroupId,
CredentialId: tt.expected.CredentialId,
RotateWhenChanged: types.MapNull(types.StringType),
}
err := mapFields(tt.input, model, "eu01")
if !tt.isValid && err == nil {
Expand Down Expand Up @@ -175,6 +180,7 @@ func TestEnableProject(t *testing.T) {
AccessKey: types.StringNull(),
SecretAccessKey: types.StringNull(),
ExpirationTimestamp: types.StringNull(),
RotateWhenChanged: types.MapNull(types.StringType),
},
false,
true,
Expand All @@ -190,6 +196,7 @@ func TestEnableProject(t *testing.T) {
AccessKey: types.StringNull(),
SecretAccessKey: types.StringNull(),
ExpirationTimestamp: types.StringNull(),
RotateWhenChanged: types.MapNull(types.StringType),
},
true,
false,
Expand All @@ -205,6 +212,7 @@ func TestEnableProject(t *testing.T) {
ProjectId: tt.expected.ProjectId,
CredentialsGroupId: tt.expected.CredentialsGroupId,
CredentialId: tt.expected.CredentialId,
RotateWhenChanged: types.MapNull(types.StringType),
}
err := enableProject(context.Background(), model, "eu01", client)
if !tt.isValid && err == nil {
Expand Down Expand Up @@ -255,6 +263,7 @@ func TestReadCredentials(t *testing.T) {
SecretAccessKey: types.StringNull(),
ExpirationTimestamp: types.StringValue(now.Format(time.RFC3339)),
Region: types.StringValue("eu01"),
RotateWhenChanged: types.MapNull(types.StringType),
},
true,
false,
Expand Down Expand Up @@ -291,6 +300,7 @@ func TestReadCredentials(t *testing.T) {
SecretAccessKey: types.StringNull(),
ExpirationTimestamp: types.StringValue(now.Format(time.RFC3339)),
Region: types.StringValue("eu01"),
RotateWhenChanged: types.MapNull(types.StringType),
},
true,
false,
Expand Down Expand Up @@ -327,6 +337,7 @@ func TestReadCredentials(t *testing.T) {
SecretAccessKey: types.StringNull(),
ExpirationTimestamp: types.StringValue(now.Format(time.RFC3339)),
Region: types.StringValue("eu01"),
RotateWhenChanged: types.MapNull(types.StringType),
},
true,
false,
Expand All @@ -338,7 +349,8 @@ func TestReadCredentials(t *testing.T) {
AccessKeys: []objectstorage.AccessKey{},
},
Model{
Region: types.StringValue("eu01"),
Region: types.StringValue("eu01"),
RotateWhenChanged: types.MapNull(types.StringType),
},
false,
false,
Expand Down Expand Up @@ -369,7 +381,8 @@ func TestReadCredentials(t *testing.T) {
},
},
Model{
Region: types.StringValue("eu01"),
Region: types.StringValue("eu01"),
RotateWhenChanged: types.MapNull(types.StringType),
},
false,
false,
Expand Down Expand Up @@ -431,6 +444,7 @@ func TestReadCredentials(t *testing.T) {
ProjectId: tt.expectedModel.ProjectId,
CredentialsGroupId: tt.expectedModel.CredentialsGroupId,
CredentialId: tt.expectedModel.CredentialId,
RotateWhenChanged: types.MapNull(types.StringType),
}
found, err := readCredentials(context.Background(), model, "eu01", client)
if !tt.isValid && err == nil {
Expand Down
Loading