Skip to content
Open
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
48 changes: 48 additions & 0 deletions charts/rustfs-operator-crds/templates/buckets.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,54 @@ spec:
- Delete
- Retain
type: string
lifecycle:
description: |-
Lifecycle rules; unset means unmanaged (the operator never reads or writes
the bucket's lifecycle configuration). An explicitly EMPTY list is not the
same thing: it means "no rules", and removes any configuration present on
the bucket.
items:
description: |-
A single S3 lifecycle rule.

Deliberately a *subset* of the S3 lifecycle spec: expiration by age and
aborting stale multipart uploads. Transitions and non-current-version rules
are omitted — they only make sense with storage tiers / versioning, which
RustFS does not offer, and a CRD field that silently does nothing is worse
than no field.
properties:
abortIncompleteMultipartUploadDays:
description: |-
Abort multipart uploads left incomplete for this many days. Worth setting
on any bucket that takes large writes: aborted parts are invisible to a
normal LIST and still consume the disk.
format: int32
nullable: true
type: integer
expirationDays:
description: Expire objects this many days after creation.
format: int32
nullable: true
type: integer
id:
description: Rule ID, unique within the bucket.
type: string
prefix:
description: Object-key prefix the rule applies to. Unset = the whole bucket.
nullable: true
type: string
status:
default: Enabled
description: Enabled (default) or Disabled.
enum:
- Enabled
- Disabled
type: string
required:
- id
type: object
nullable: true
type: array
quotaBytes:
description: Hard quota in bytes; unset means unmanaged.
format: uint64
Expand Down
48 changes: 48 additions & 0 deletions charts/rustfs-operator/crds/crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,54 @@ spec:
- Delete
- Retain
type: string
lifecycle:
description: |-
Lifecycle rules; unset means unmanaged (the operator never reads or writes
the bucket's lifecycle configuration). An explicitly EMPTY list is not the
same thing: it means "no rules", and removes any configuration present on
the bucket.
items:
description: |-
A single S3 lifecycle rule.

Deliberately a *subset* of the S3 lifecycle spec: expiration by age and
aborting stale multipart uploads. Transitions and non-current-version rules
are omitted — they only make sense with storage tiers / versioning, which
RustFS does not offer, and a CRD field that silently does nothing is worse
than no field.
properties:
abortIncompleteMultipartUploadDays:
description: |-
Abort multipart uploads left incomplete for this many days. Worth setting
on any bucket that takes large writes: aborted parts are invisible to a
normal LIST and still consume the disk.
format: int32
nullable: true
type: integer
expirationDays:
description: Expire objects this many days after creation.
format: int32
nullable: true
type: integer
id:
description: Rule ID, unique within the bucket.
type: string
prefix:
description: Object-key prefix the rule applies to. Unset = the whole bucket.
nullable: true
type: string
status:
default: Enabled
description: Enabled (default) or Disabled.
enum:
- Enabled
- Disabled
type: string
required:
- id
type: object
nullable: true
type: array
quotaBytes:
description: Hard quota in bytes; unset means unmanaged.
format: uint64
Expand Down
48 changes: 48 additions & 0 deletions deploy/crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,54 @@ spec:
- Delete
- Retain
type: string
lifecycle:
description: |-
Lifecycle rules; unset means unmanaged (the operator never reads or writes
the bucket's lifecycle configuration). An explicitly EMPTY list is not the
same thing: it means "no rules", and removes any configuration present on
the bucket.
items:
description: |-
A single S3 lifecycle rule.

Deliberately a *subset* of the S3 lifecycle spec: expiration by age and
aborting stale multipart uploads. Transitions and non-current-version rules
are omitted — they only make sense with storage tiers / versioning, which
RustFS does not offer, and a CRD field that silently does nothing is worse
than no field.
properties:
abortIncompleteMultipartUploadDays:
description: |-
Abort multipart uploads left incomplete for this many days. Worth setting
on any bucket that takes large writes: aborted parts are invisible to a
normal LIST and still consume the disk.
format: int32
nullable: true
type: integer
expirationDays:
description: Expire objects this many days after creation.
format: int32
nullable: true
type: integer
id:
description: Rule ID, unique within the bucket.
type: string
prefix:
description: Object-key prefix the rule applies to. Unset = the whole bucket.
nullable: true
type: string
status:
default: Enabled
description: Enabled (default) or Disabled.
enum:
- Enabled
- Disabled
type: string
required:
- id
type: object
nullable: true
type: array
quotaBytes:
description: Hard quota in bytes; unset means unmanaged.
format: uint64
Expand Down
44 changes: 44 additions & 0 deletions src/crd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,44 @@ pub enum DeletionPolicy {
Retain,
}

/// Whether a lifecycle rule is active.
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)]
pub enum LifecycleStatus {
/// The rule is evaluated by the scanner.
#[default]
Enabled,
/// The rule is kept but not evaluated.
Disabled,
}

/// A single S3 lifecycle rule.
///
/// Deliberately a *subset* of the S3 lifecycle spec: expiration by age and
/// aborting stale multipart uploads. Transitions and non-current-version rules
/// are omitted — they only make sense with storage tiers / versioning, which
/// RustFS does not offer, and a CRD field that silently does nothing is worse
/// than no field.
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct LifecycleRuleSpec {
/// Rule ID, unique within the bucket.
pub id: String,
/// Enabled (default) or Disabled.
#[serde(default)]
pub status: LifecycleStatus,
/// Object-key prefix the rule applies to. Unset = the whole bucket.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub prefix: Option<String>,
/// Expire objects this many days after creation.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expiration_days: Option<i32>,
/// Abort multipart uploads left incomplete for this many days. Worth setting
/// on any bucket that takes large writes: aborted parts are invisible to a
/// normal LIST and still consume the disk.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub abort_incomplete_multipart_upload_days: Option<i32>,
}

/// Shared status for all RustFS resources.
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
Expand Down Expand Up @@ -153,6 +191,12 @@ pub struct BucketSpec {
/// Hard quota in bytes; unset means unmanaged.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub quota_bytes: Option<u64>,
/// Lifecycle rules; unset means unmanaged (the operator never reads or writes
/// the bucket's lifecycle configuration). An explicitly EMPTY list is not the
/// same thing: it means "no rules", and removes any configuration present on
/// the bucket.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub lifecycle: Option<Vec<LifecycleRuleSpec>>,
#[serde(default)]
pub deletion_policy: DeletionPolicy,
}
Expand Down
23 changes: 23 additions & 0 deletions src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use async_trait::async_trait;
use rc_core::admin::{
AdminApi, CreateServiceAccountRequest, Policy, PolicyEntity, ServiceAccount, User, UserStatus,
};
use rc_core::lifecycle::LifecycleRule;
use rc_core::traits::ObjectStore;
use rc_core::{Alias, Error as RcError};
use rc_s3::{AdminClient, S3Client};
Expand Down Expand Up @@ -56,6 +57,13 @@ pub trait RustFs: Send + Sync {
async fn set_versioning(&self, bucket: &str, enabled: bool) -> Result<()>;
async fn get_bucket_quota(&self, bucket: &str) -> Result<Option<u64>>;
async fn set_bucket_quota(&self, bucket: &str, quota: u64) -> Result<()>;
/// Current lifecycle rules. Empty when the bucket has no configuration.
async fn get_bucket_lifecycle(&self, bucket: &str) -> Result<Vec<LifecycleRule>>;
/// Replace the bucket's lifecycle configuration. RustFS has no per-rule API —
/// the whole configuration is written at once.
async fn set_bucket_lifecycle(&self, bucket: &str, rules: Vec<LifecycleRule>) -> Result<()>;
/// Remove the lifecycle configuration entirely.
async fn delete_bucket_lifecycle(&self, bucket: &str) -> Result<()>;

// Users
async fn get_user(&self, access_key: &str) -> Result<Option<User>>;
Expand Down Expand Up @@ -204,6 +212,21 @@ impl RustFs for RustFsProvider {
Ok(())
}

async fn get_bucket_lifecycle(&self, bucket: &str) -> Result<Vec<LifecycleRule>> {
Self::cli(&format!("ilm rule list $ALIAS/{bucket}"));
Ok(self.s3.get_bucket_lifecycle(bucket).await?)
}

async fn set_bucket_lifecycle(&self, bucket: &str, rules: Vec<LifecycleRule>) -> Result<()> {
Self::cli(&format!("ilm rule add $ALIAS/{bucket} (x{})", rules.len()));
Ok(self.s3.set_bucket_lifecycle(bucket, rules).await?)
}

async fn delete_bucket_lifecycle(&self, bucket: &str) -> Result<()> {
Self::cli(&format!("ilm rule rm --all --force $ALIAS/{bucket}"));
Ok(self.s3.delete_bucket_lifecycle(bucket).await?)
}

async fn get_user(&self, access_key: &str) -> Result<Option<User>> {
Self::cli(&format!("admin user info $ALIAS {access_key}"));
optional(self.admin.get_user(access_key).await)
Expand Down
Loading