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
119 changes: 112 additions & 7 deletions coreweave/object_storage/resource_bucket_lifecycle_configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package objectstorage
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"slices"
Expand All @@ -16,12 +17,17 @@ import (

"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/hcl/v2/hclwrite"
"github.com/hashicorp/terraform-plugin-framework-validators/int32validator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/zclconf/go-cty/cty"
)

Expand Down Expand Up @@ -53,13 +59,15 @@ type BucketLifecycleResourceModel struct {

// LifecycleRuleModel maps a single lifecycle rule block.
type LifecycleRuleModel struct {
ID types.String `tfsdk:"id"`
Prefix types.String `tfsdk:"prefix"`
Status types.String `tfsdk:"status"`
Expiration *ExpirationModel `tfsdk:"expiration"`
NoncurrentVersionExpiration *NoncurrentVersionExpirationModel `tfsdk:"noncurrent_version_expiration"`
AbortIncompleteMultipart *AbortIncompleteMultipartModel `tfsdk:"abort_incomplete_multipart_upload"`
Filter *FilterModel `tfsdk:"filter"`
ID types.String `tfsdk:"id"`
Prefix types.String `tfsdk:"prefix"`
Status types.String `tfsdk:"status"`
Expiration *ExpirationModel `tfsdk:"expiration"`
Transitions []*TransitionModel `tfsdk:"transition"`
NoncurrentVersionExpiration *NoncurrentVersionExpirationModel `tfsdk:"noncurrent_version_expiration"`
NoncurrentVersionTransitions []*NoncurrentVersionTransitionModel `tfsdk:"noncurrent_version_transition"`
AbortIncompleteMultipart *AbortIncompleteMultipartModel `tfsdk:"abort_incomplete_multipart_upload"`
Filter *FilterModel `tfsdk:"filter"`
}

// ExpirationModel maps the expiration sub-block.
Expand All @@ -69,12 +77,26 @@ type ExpirationModel struct {
ExpiredObjectDeleteMarker types.Bool `tfsdk:"expired_object_delete_marker"`
}

// TransitionModel maps the transition sub-block.
type TransitionModel struct {
Date types.String `tfsdk:"date"`
Days types.Int32 `tfsdk:"days"`
StorageClass types.String `tfsdk:"storage_class"`
}

// NoncurrentVersionExpirationModel maps the noncurrent_version_expiration sub-block.
type NoncurrentVersionExpirationModel struct {
NoncurrentDays types.Int32 `tfsdk:"noncurrent_days"`
NewerNoncurrentVersions types.Int32 `tfsdk:"newer_noncurrent_versions"`
}

// NoncurrentVersionTransitionModel maps the noncurrent_version_transition sub-block.
type NoncurrentVersionTransitionModel struct {
NoncurrentDays types.Int32 `tfsdk:"noncurrent_days"`
NewerNoncurrentVersions types.Int32 `tfsdk:"newer_noncurrent_versions"`
StorageClass types.String `tfsdk:"storage_class"`
}

// AbortIncompleteMultipartModel maps the abort_incomplete_multipart_upload sub-block.
type AbortIncompleteMultipartModel struct {
DaysAfterInitiation types.Int32 `tfsdk:"days_after_initiation"`
Expand Down Expand Up @@ -157,6 +179,7 @@ func (r *BucketLifecycleResource) Schema(ctx context.Context, req resource.Schem
},
"days": schema.Int32Attribute{
Optional: true,
Validators: []validator.Int32{int32validator.AtLeast(0)},
MarkdownDescription: "Number of days after object creation for expiration",
},
"expired_object_delete_marker": schema.BoolAttribute{
Expand Down Expand Up @@ -229,6 +252,51 @@ func (r *BucketLifecycleResource) Schema(ctx context.Context, req resource.Schem
},
},
},
"noncurrent_version_transition": schema.SetNestedBlock{
NestedObject: schema.NestedBlockObject{
Attributes: map[string]schema.Attribute{
"newer_noncurrent_versions": schema.Int32Attribute{
Optional: true,
Validators: []validator.Int32{int32validator.AtLeast(1)},
MarkdownDescription: "Number of noncurrent versions to retain",
},
"noncurrent_days": schema.Int32Attribute{
Required: true,
Validators: []validator.Int32{int32validator.AtLeast(0)},
MarkdownDescription: "Number of days after object becomes noncurrent before the transition may occur",
},
"storage_class": schema.StringAttribute{
Required: true,
MarkdownDescription: "Storage class to transition noncurrent objects to",
},
},
},
},
"transition": schema.SetNestedBlock{
NestedObject: schema.NestedBlockObject{
Attributes: map[string]schema.Attribute{
"date": schema.StringAttribute{
Optional: true,
Validators: []validator.String{
stringvalidator.ConflictsWith(path.MatchRelative().AtParent().AtName("days")),
},
MarkdownDescription: "ISO8601 date when objects transition",
},
"days": schema.Int32Attribute{
Optional: true,
Validators: []validator.Int32{
int32validator.ConflictsWith(path.MatchRelative().AtParent().AtName("date")),
int32validator.AtLeast(0),
},
MarkdownDescription: "Number of days after object creation for transition",
},
"storage_class": schema.StringAttribute{
Required: true,
MarkdownDescription: "Storage class to transition objects to",
},
},
},
},
},
},
},
Expand Down Expand Up @@ -279,6 +347,16 @@ func expandRules(ctx context.Context, in []LifecycleRuleModel) []s3types.Lifecyc
}
rule.Expiration = &exp
}
for _, transition := range r.Transitions {
t := s3types.Transition{
StorageClass: s3types.TransitionStorageClass(transition.StorageClass.ValueString()),
Days: transition.Days.ValueInt32Pointer(),
}
if !transition.Date.IsNull() {
t.Date = aws.Time(parseISO8601(transition.Date.ValueString()))
}
rule.Transitions = append(rule.Transitions, t)
}
if r.NoncurrentVersionExpiration != nil {
nc := s3types.NoncurrentVersionExpiration{}
if !r.NoncurrentVersionExpiration.NoncurrentDays.IsNull() {
Expand Down Expand Up @@ -515,6 +593,12 @@ func (r *BucketLifecycleResource) Create(ctx context.Context, req resource.Creat
}

rules := expandRules(ctx, data.Rule)
rulesJSON, err := json.Marshal(rules)
if err != nil {
resp.Diagnostics.AddError("Failed to marshal lifecycle rules to JSON", err.Error())
return
}
tflog.Debug(ctx, "creating lifecycle rules for bucket", map[string]any{"rules": string(rulesJSON), "bucket": data.Bucket.ValueString()})
lifecycleConfig := &s3types.BucketLifecycleConfiguration{
Rules: rules,
}
Expand Down Expand Up @@ -567,13 +651,34 @@ func flattenLifecycleRules(in []s3types.LifecycleRule) []LifecycleRuleModel {
mdl.Expiration = expiration
}

for _, t := range r.Transitions {
transition := &TransitionModel{
Date: types.StringNull(),
Days: types.Int32PointerValue(t.Days),
StorageClass: types.StringValue(string(t.StorageClass)),
}
if t.Date != nil {
transition.Date = types.StringValue(t.Date.Format(time.RFC3339))
}
mdl.Transitions = append(mdl.Transitions, transition)
}

if r.NoncurrentVersionExpiration != nil {
mdl.NoncurrentVersionExpiration = &NoncurrentVersionExpirationModel{
NoncurrentDays: types.Int32PointerValue(r.NoncurrentVersionExpiration.NoncurrentDays),
NewerNoncurrentVersions: types.Int32PointerValue(r.NoncurrentVersionExpiration.NewerNoncurrentVersions),
}
}

for _, nct := range r.NoncurrentVersionTransitions {
ncTransition := &NoncurrentVersionTransitionModel{
NoncurrentDays: types.Int32PointerValue(nct.NoncurrentDays),
StorageClass: types.StringValue(string(nct.StorageClass)),
NewerNoncurrentVersions: types.Int32PointerValue(nct.NewerNoncurrentVersions),
}
mdl.NoncurrentVersionTransitions = append(mdl.NoncurrentVersionTransitions, ncTransition)
}

if r.AbortIncompleteMultipartUpload != nil {
mdl.AbortIncompleteMultipart = &AbortIncompleteMultipartModel{
DaysAfterInitiation: types.Int32PointerValue(r.AbortIncompleteMultipartUpload.DaysAfterInitiation),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,26 @@ func TestBucketLifecycleConfiguration(t *testing.T) {
NewerNoncurrentVersions: types.Int32Value(2),
},
}
transitionOnly := objectstorage.LifecycleRuleModel{
ID: types.StringValue("transition-only"),
Status: types.StringValue("Enabled"),
Transitions: []*objectstorage.TransitionModel{
{
Days: types.Int32Value(30),
StorageClass: types.StringValue("STANDARD_IA"),
},
},
}
noncurrentTransitionOnly := objectstorage.LifecycleRuleModel{
ID: types.StringValue("noncurrent-transition-only"),
Status: types.StringValue("Enabled"),
NoncurrentVersionTransitions: []*objectstorage.NoncurrentVersionTransitionModel{
{
NoncurrentDays: types.Int32Value(30),
StorageClass: types.StringValue("STANDARD_IA"),
},
},
}
abortOnly := objectstorage.LifecycleRuleModel{
ID: types.StringValue("abort-only"),
Status: types.StringValue("Enabled"),
Expand Down Expand Up @@ -340,6 +360,30 @@ func TestBucketLifecycleConfiguration(t *testing.T) {
},
},
}),
createLifecycleTestStep(ctx, t, lifecycleTestConfig{
name: "transition only",
resourceName: resourceName,
bucket: bucket,
bucketVersioning: versioning,
rules: []objectstorage.LifecycleRuleModel{transitionOnly},
configPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectResourceAction(fmt.Sprintf("coreweave_object_storage_bucket_lifecycle_configuration.%s", resourceName), plancheck.ResourceActionUpdate),
},
},
}),
createLifecycleTestStep(ctx, t, lifecycleTestConfig{
name: "noncurrent transition only",
resourceName: resourceName,
bucket: bucket,
bucketVersioning: versioning,
rules: []objectstorage.LifecycleRuleModel{noncurrentTransitionOnly},
configPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectResourceAction(fmt.Sprintf("coreweave_object_storage_bucket_lifecycle_configuration.%s", resourceName), plancheck.ResourceActionUpdate),
},
},
}),
createLifecycleTestStep(ctx, t, lifecycleTestConfig{
name: "abort only",
resourceName: resourceName,
Expand Down
28 changes: 28 additions & 0 deletions docs/resources/object_storage_bucket_lifecycle_configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@ Optional:
- `filter` (Block, Optional) (see [below for nested schema](#nestedblock--rule--filter))
- `id` (String) Unique identifier for the rule
- `noncurrent_version_expiration` (Block, Optional) (see [below for nested schema](#nestedblock--rule--noncurrent_version_expiration))
- `noncurrent_version_transition` (Block Set) (see [below for nested schema](#nestedblock--rule--noncurrent_version_transition))
- `prefix` (String) Object key prefix to which the rule applies
- `transition` (Block Set) (see [below for nested schema](#nestedblock--rule--transition))

<a id="nestedblock--rule--abort_incomplete_multipart_upload"></a>
### Nested Schema for `rule.abort_incomplete_multipart_upload`
Expand Down Expand Up @@ -160,6 +162,32 @@ Optional:
- `newer_noncurrent_versions` (Number) Number of noncurrent versions to retain
- `noncurrent_days` (Number) Days after becoming noncurrent before deletion


<a id="nestedblock--rule--noncurrent_version_transition"></a>
### Nested Schema for `rule.noncurrent_version_transition`

Required:

- `noncurrent_days` (Number) Number of days after object becomes noncurrent before the transition may occur
- `storage_class` (String) Storage class to transition noncurrent objects to

Optional:

- `newer_noncurrent_versions` (Number) Number of noncurrent versions to retain


<a id="nestedblock--rule--transition"></a>
### Nested Schema for `rule.transition`

Required:

- `storage_class` (String) Storage class to transition objects to

Optional:

- `date` (String) ISO8601 date when objects transition
- `days` (Number) Number of days after object creation for transition

## Import

Import is supported using the following syntax:
Expand Down
1 change: 0 additions & 1 deletion docs/resources/object_storage_bucket_versioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ resource "coreweave_object_storage_bucket_versioning" "default" {
versioning_configuration {
status = "Enabled"
}

}
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,4 @@ resource "coreweave_object_storage_bucket_versioning" "default" {
versioning_configuration {
status = "Enabled"
}

}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ require (
github.com/hashicorp/go-uuid v1.0.3
github.com/hashicorp/hcl/v2 v2.23.0
github.com/hashicorp/terraform-plugin-framework v1.15.1
github.com/hashicorp/terraform-plugin-framework-validators v0.18.0
github.com/hashicorp/terraform-plugin-go v0.27.0
github.com/hashicorp/terraform-plugin-log v0.9.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ github.com/hashicorp/terraform-json v0.23.0 h1:sniCkExU4iKtTADReHzACkk8fnpQXrdD2
github.com/hashicorp/terraform-json v0.23.0/go.mod h1:MHdXbBAbSg0GvzuWazEGKAn/cyNfIB7mN6y7KJN6y2c=
github.com/hashicorp/terraform-plugin-framework v1.15.1 h1:2mKDkwb8rlx/tvJTlIcpw0ykcmvdWv+4gY3SIgk8Pq8=
github.com/hashicorp/terraform-plugin-framework v1.15.1/go.mod h1:hxrNI/GY32KPISpWqlCoTLM9JZsGH3CyYlir09bD/fI=
github.com/hashicorp/terraform-plugin-framework-validators v0.18.0 h1:OQnlOt98ua//rCw+QhBbSqfW3QbwtVrcdWeQN5gI3Hw=
github.com/hashicorp/terraform-plugin-framework-validators v0.18.0/go.mod h1:lZvZvagw5hsJwuY7mAY6KUz45/U6fiDR0CzQAwWD0CA=
github.com/hashicorp/terraform-plugin-go v0.27.0 h1:ujykws/fWIdsi6oTUT5Or4ukvEan4aN9lY+LOxVP8EE=
github.com/hashicorp/terraform-plugin-go v0.27.0/go.mod h1:FDa2Bb3uumkTGSkTFpWSOwWJDwA7bf3vdP3ltLDTH6o=
github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0=
Expand Down
Loading