Skip to content

feat(provisioning): support transition actions in bucket lifecycle - #281

Merged
GatewayJ merged 1 commit into
rustfs:mainfrom
shurkus:feat/issue-279-lifecycle-transition
Oct 17, 2026
Merged

GatewayJ merged 1 commit into
rustfs:mainfrom
shurkus:feat/issue-279-lifecycle-transition

Conversation

@shurkus

@shurkus shurkus commented Oct 16, 2026

Copy link
Copy Markdown

Type of Change

  • New Feature
  • Bug Fix
  • Documentation
  • Performance Improvement
  • Test/CI
  • Refactor
  • Other:

Related Issues

Closes #279

Summary of Changes

Bucket lifecycle rules in spec.buckets[].lifecycle can now declare transition and noncurrentVersionTransition, so ILM tiering rules are managed by the operator together with the other lifecycle rules of the bucket.

lifecycle:
  state: Present
  rules:
    - id: to-cold
      status: Enabled
      filter:
        prefix: ""
      transition:
        days: 7
        storageClass: COLD
  • CRD: transition {days, storageClass} and noncurrentVersionTransition {noncurrentDays, storageClass}, both with days >= 0 as RustFS validates them (Transition/NoncurrentVersionTransition reject only negative days). The rule-level CEL rule now accepts any of the four actions.
  • Validation: storageClass is checked against the RustFS tier-name rules (uppercase, not STANDARD or REDUCED_REDUNDANCY, valid XML text), so a typo fails with a clear status message.
  • Tiers that do not exist yet: tiers are registered outside the operator (registering them is Feature request: remote storage tiers in the Tenant CRD #280), often after the Tenant is provisioned. RustFS rejects a lifecycle that references an unknown tier with a generic 400 InvalidArgument: invalid tier., which would leave the bucket Failed until the spec changes. Before applying a lifecycle that references tiers, the operator lists them with GET /rustfs/admin/v3/tier; while one is missing the bucket stays Pending with a message naming the tier and the run requeues, the same way a user whose referenced policy is not ready is handled. The lookup runs only on the apply path, not when the live configuration already matches. Only tier names are read from the response; endpoints and credentials are dropped while parsing.
  • Ownership hash: rustfs-admin serializes the new elements in the order RustFS returns them from GetBucketLifecycleConfiguration (... ID, NoncurrentVersionTransition, Status, Transition). The ownership check compares canonical XML without reordering, so this keeps the hash of an applied configuration equal to the live one and the rule is adopted instead of re-applied on every reconcile. Rules without transitions serialize byte-for-byte as before, so existing ownership hashes are unaffected.
  • OpenAPI schemas, console types, CRD YAML, chart README, both user guides and CHANGELOG are updated. examples/provisioning-tenant.yaml is unchanged on purpose: a transition needs a tier that the example does not create, so the bucket would stay Pending for anyone applying the example as is.

Checklist

  • I have read and followed the CONTRIBUTING.md guidelines
  • Passed make pre-commit (fmt-check + clippy + test + console-lint + console-fmt-check)
  • Added/updated necessary tests
  • Documentation updated (if needed)
  • CHANGELOG.md updated under [Unreleased] (if user-visible change)
  • CI/CD passed (if applicable)

Impact

  • Breaking change (CRD/API compatibility)
  • Requires doc/config/deployment update
  • Other impact:

The CRD change is additive. The only changed existing text is the CEL message of the rule-level validation, which now lists all four actions.

Verification

make pre-commit
cargo nextest run --all --no-tests pass
cargo test -p rustfs-admin -- lifecycle tier
cargo test --lib lifecycle
helm lint deploy/rustfs-operator && helm lint deploy/rustfs-tenant

New tests:

  • rustfs-admin: lifecycle_xml_with_transitions_matches_rustfs_get_response_shape, lifecycle_xml_transition_allows_zero_days_and_escapes_storage_class, tier_names_are_read_from_the_backend_object_and_secrets_are_ignored, tier_list_without_a_backend_name_is_rejected.
  • Reconcile: lifecycle_configuration_validates_transition_fields, transition_bucket_lifecycle_is_applied_when_live_configuration_is_missing, transition_bucket_lifecycle_waits_for_an_unregistered_tier, rustfs_shaped_live_transition_lifecycle_is_adopted_without_write; the existing expiration apply test now also asserts that rules without transitions never query tiers.
  • Types: bucket_lifecycle_deserializes_transition_actions and CRD schema assertions for the new minimums and the CEL message.

The fixtures come from a live RustFS 1.0.2-preview.2: a lifecycle sent with its elements scrambled came back from GetBucketLifecycleConfiguration in the order above, a rule with an unregistered StorageClass was rejected with 400 InvalidArgument: invalid tier., and the tier list has the {"type": "s3", "s3": {"name": ...}} shape used in the parser test.

Run in containers matching CI: Rust stable in rust:1-bookworm with Helm 3.21.3 and cargo-nextest, console-web in node:24 with pnpm 10.28.1. All make pre-commit targets passed, cargo nextest run --all ran 768 tests with 768 passing, helm lint passed for both charts, and helm template of the default operator chart renders.

Live check on a staging cluster. An operator image built from this branch (the operator binary on top of rustfs/operator:0.0.8) with the CRD from this branch, managing a RustFS 1.0.2-preview.2 Tenant whose STORJ tier points at Storj:

Scenario Result
Bucket with transition: {days: 0, storageClass: STORJ} Ready; the live document is exactly the one the operator sent; a new object transitioned (x-amz-storage-class: STORJ) and GET returned the original bytes
Bucket with transition to LATE, a tier that did not exist yet Pending with waiting for RustFS tier(s) LATE referenced by bucket lifecycle, no lifecycle PUT
LATE registered afterwards with rc ilm tier add The bucket became Ready about 6 s later without any spec change
Two forced reconciles over three minutes Ownership hashes unchanged, no further lifecycle PUT, no operator errors
Another Tenant without lifecycle on the same operator Stayed Ready

While a tier is missing the Tenant reports Ready=False with ProvisioningPending, the same as for a user whose referenced policy is not ready.

Additional Notes

  • lifecycle_canonicalization_preserves_unknown_actions used Transition as its example of an action the operator does not model. It now uses NoncurrentVersionExpiration, which is still unmodeled, so the test keeps checking the same property.
  • Date-based transitions (Transition.Date) and NewerNoncurrentVersions are not modeled; they can follow if there is interest.

Thank you for your contribution! Please ensure your PR follows the community standards (CODE_OF_CONDUCT.md) and sign the CLA if this is your first contribution.

Bucket lifecycle rules accept `transition` (current versions) and
`noncurrentVersionTransition` (noncurrent versions), both after zero or
more days, matching RustFS validation. `storageClass` names a tier that
is registered in RustFS; the operator checks it against RustFS tier-name
rules (uppercase, not STANDARD or REDUCED_REDUNDANCY) and does not
register tiers itself.

Before applying a lifecycle that references tiers, the operator lists
them via GET /rustfs/admin/v3/tier. A missing tier keeps the bucket
Pending and requeues instead of failing on RustFS's generic 400, so a
tier registered after the rule is picked up. Only tier names are read
from that response.

The admin client serializes the new elements in the order RustFS
returns from GetBucketLifecycleConfiguration, so the canonical ownership
hash of an applied configuration matches the live document. Rules
without transitions serialize exactly as before.

Closes rustfs#279
@GatewayJ
GatewayJ added this pull request to the merge queue Oct 17, 2026
Merged via the queue into rustfs:main with commit ca8b6d4 Oct 17, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: Transition rules in spec.buckets[].lifecycle

2 participants