From b1c6578df1c7dfa34c5faeecd3bbca125d6329c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=89=E6=AD=A3=E8=B6=85?= Date: Fri, 19 Jun 2026 07:10:52 +0800 Subject: [PATCH] refactor: narrow test harness compatibility surfaces (#3592) --- Cargo.lock | 1 + crates/e2e_test/src/storage_compat.rs | 16 +- .../bucket/lifecycle/bucket_lifecycle_ops.rs | 16 +- crates/ecstore/src/bucket/lifecycle/core.rs | 5 +- .../src/bucket/lifecycle/tier_sweeper.rs | 3 +- crates/ecstore/src/set_disk.rs | 2 +- crates/ecstore/src/store/object.rs | 2 +- crates/ecstore/src/store_api.rs | 8 +- crates/heal/tests/common/storage_compat.rs | 22 ++- crates/notify/Cargo.toml | 1 + crates/notify/src/storage_compat.rs | 2 +- crates/policy/src/policy/action.rs | 13 +- crates/scanner/tests/common/storage_compat.rs | 66 +++++++- .../tests/lifecycle_integration_test.rs | 4 +- crates/storage-api/src/lib.rs | 1 + crates/storage-api/src/object.rs | 27 ++++ .../admin-route-action-snapshot.md | 4 +- docs/architecture/compat-cleanup-register.md | 7 +- docs/architecture/crate-boundaries.md | 5 + docs/architecture/migration-progress.md | 149 ++++++++++++++++-- fuzz/Cargo.lock | 1 + fuzz/fuzz_targets/bucket_validation.rs | 16 +- .../bucket_validation/storage_compat.rs | 23 +++ fuzz/fuzz_targets/path_containment.rs | 1 + .../{ => path_containment}/storage_compat.rs | 8 +- rustfs/src/admin/handlers/kms_keys.rs | 34 +--- scripts/check_architecture_migration_rules.sh | 52 ++++-- 27 files changed, 384 insertions(+), 105 deletions(-) create mode 100644 fuzz/fuzz_targets/bucket_validation/storage_compat.rs rename fuzz/fuzz_targets/{ => path_containment}/storage_compat.rs (70%) diff --git a/Cargo.lock b/Cargo.lock index 485ff48a7..a6de82c6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9702,6 +9702,7 @@ dependencies = [ "rustfs-ecstore", "rustfs-s3-ops", "rustfs-s3-types", + "rustfs-storage-api", "rustfs-targets", "rustfs-utils", "serde", diff --git a/crates/e2e_test/src/storage_compat.rs b/crates/e2e_test/src/storage_compat.rs index 60866f84d..08dda0d59 100644 --- a/crates/e2e_test/src/storage_compat.rs +++ b/crates/e2e_test/src/storage_compat.rs @@ -15,5 +15,19 @@ pub(crate) mod ecstore { #![allow(unused_imports)] - pub(crate) use rustfs_ecstore::{bucket, disk, rpc}; + pub(crate) mod bucket { + pub(crate) mod bucket_target_sys { + pub(crate) use rustfs_ecstore::bucket::bucket_target_sys::BucketTargetSys; + } + } + + pub(crate) mod disk { + pub(crate) use rustfs_ecstore::disk::{VolumeInfo, WalkDirOptions}; + } + + pub(crate) mod rpc { + pub(crate) use rustfs_ecstore::rpc::{ + TonicInterceptor, gen_tonic_signature_interceptor, node_service_time_out_client, node_service_time_out_client_no_auth, + }; + } } diff --git a/crates/ecstore/src/bucket/lifecycle/bucket_lifecycle_ops.rs b/crates/ecstore/src/bucket/lifecycle/bucket_lifecycle_ops.rs index d473c91ce..95eabe555 100644 --- a/crates/ecstore/src/bucket/lifecycle/bucket_lifecycle_ops.rs +++ b/crates/ecstore/src/bucket/lifecycle/bucket_lifecycle_ops.rs @@ -15,7 +15,7 @@ use crate::bucket::lifecycle::bucket_lifecycle_audit::{LcAuditEvent, LcEventSrc}; use crate::bucket::lifecycle::evaluator::Evaluator; use crate::bucket::lifecycle::lifecycle::{ - self, ExpirationOptions, Lifecycle, ObjectOpts, TransitionOptions, abort_incomplete_multipart_upload_due, + self, Lifecycle, ObjectOpts, TransitionOptions, abort_incomplete_multipart_upload_due, }; use crate::bucket::lifecycle::tier_delete_journal::{process_tier_delete_journal_entry, run_tier_delete_journal_recovery_loop}; use crate::bucket::lifecycle::tier_free_version_recovery::{DEFAULT_FREE_VERSION_RECOVERY_LIMIT, recover_tier_free_versions}; @@ -59,7 +59,8 @@ use rustfs_filemeta::{ }; use rustfs_s3_types::EventName; use rustfs_storage_api::{ - DeletedObject, HTTPRangeSpec, ListOperations as _, MultipartOperations as _, ObjectOperations as _, ObjectToDelete, + DeletedObject, ExpirationOptions, HTTPRangeSpec, ListOperations as _, MultipartOperations as _, ObjectOperations as _, + ObjectToDelete, }; use rustfs_utils::{get_env_i64, get_env_usize, path::encode_dir_object, string::strings_has_prefix_fold}; use s3s::dto::{ @@ -379,14 +380,7 @@ pub trait ExpiryOp: 'static { fn as_any(&self) -> &dyn Any; } -#[derive(Debug, Default, Clone)] -pub struct TransitionedObject { - pub name: String, - pub version_id: String, - pub tier: String, - pub free_version: bool, - pub status: String, -} +pub use rustfs_storage_api::TransitionedObject; struct FreeVersionTask(ObjectInfo); @@ -2862,7 +2856,6 @@ mod tests { transitioned_cleanup_tuple, }; use crate::bucket::lifecycle::bucket_lifecycle_audit::LcEventSrc; - use crate::bucket::lifecycle::core::ExpirationOptions; use crate::bucket::lifecycle::tier_sweeper::Jentry; use crate::bucket::metadata::BUCKET_LIFECYCLE_CONFIG; use crate::bucket::metadata_sys; @@ -2877,6 +2870,7 @@ mod tests { use rustfs_common::metrics::{IlmAction, global_metrics}; use rustfs_config::ENV_TRANSITION_WORKERS_ABSOLUTE_MAX; use rustfs_filemeta::{ReplicateDecision, VersionPurgeStatusType}; + use rustfs_storage_api::ExpirationOptions; use rustfs_storage_api::{BucketOperations, BucketOptions, MakeBucketOptions, MultipartOperations as _}; use s3s::dto::{BucketLifecycleConfiguration, ExpirationStatus, LifecycleExpiration, LifecycleRule, Timestamp}; use serial_test::serial; diff --git a/crates/ecstore/src/bucket/lifecycle/core.rs b/crates/ecstore/src/bucket/lifecycle/core.rs index c484faf31..701d1730f 100644 --- a/crates/ecstore/src/bucket/lifecycle/core.rs +++ b/crates/ecstore/src/bucket/lifecycle/core.rs @@ -934,10 +934,7 @@ impl Default for Event { } } -#[derive(Debug, Clone, Default)] -pub struct ExpirationOptions { - pub expire: bool, -} +pub use rustfs_storage_api::ExpirationOptions; #[derive(Debug, Clone)] pub struct TransitionOptions { diff --git a/crates/ecstore/src/bucket/lifecycle/tier_sweeper.rs b/crates/ecstore/src/bucket/lifecycle/tier_sweeper.rs index 5aea99eae..e75bd9170 100644 --- a/crates/ecstore/src/bucket/lifecycle/tier_sweeper.rs +++ b/crates/ecstore/src/bucket/lifecycle/tier_sweeper.rs @@ -18,12 +18,13 @@ #![allow(unused_must_use)] #![allow(clippy::all)] -use crate::bucket::lifecycle::bucket_lifecycle_ops::{ExpiryOp, GLOBAL_ExpiryState, TransitionedObject}; +use crate::bucket::lifecycle::bucket_lifecycle_ops::{ExpiryOp, GLOBAL_ExpiryState}; use crate::bucket::lifecycle::lifecycle::{self, ObjectOpts}; use crate::bucket::lifecycle::tier_delete_journal::persist_tier_delete_journal_entry; use crate::client::signer_error::error_chain_contains_signer_header_marker; use crate::global::GLOBAL_TierConfigMgr; use crate::store::ECStore; +use rustfs_storage_api::TransitionedObject; use rustfs_utils::get_env_usize; use sha2::{Digest, Sha256}; use std::any::Any; diff --git a/crates/ecstore/src/set_disk.rs b/crates/ecstore/src/set_disk.rs index b461541b2..6b64fa6b2 100644 --- a/crates/ecstore/src/set_disk.rs +++ b/crates/ecstore/src/set_disk.rs @@ -5004,7 +5004,6 @@ pub fn is_infrequent_access_class(storage_class: &str) -> bool { #[cfg(test)] mod tests { use super::*; - use crate::bucket::lifecycle::bucket_lifecycle_ops::TransitionedObject; use crate::disk::CHECK_PART_UNKNOWN; use crate::disk::CHECK_PART_VOLUME_NOT_FOUND; use crate::disk::RUSTFS_META_BUCKET; @@ -5023,6 +5022,7 @@ mod tests { use rustfs_filemeta::ReplicationState; use rustfs_lock::client::local::LocalClient; use rustfs_lock::{LockError, LockInfo, LockResponse, LockStats}; + use rustfs_storage_api::TransitionedObject; use rustfs_storage_api::{CompletePart, NamespaceLocking as _, ObjectOperations as _}; use serial_test::serial; use std::collections::HashMap; diff --git a/crates/ecstore/src/store/object.rs b/crates/ecstore/src/store/object.rs index d84017cf5..726833a0e 100644 --- a/crates/ecstore/src/store/object.rs +++ b/crates/ecstore/src/store/object.rs @@ -1267,9 +1267,9 @@ impl ECStore { #[cfg(test)] mod tests { use super::*; - use crate::bucket::lifecycle::bucket_lifecycle_ops::TransitionedObject; use crate::bucket::lifecycle::core::TRANSITION_COMPLETE; use bytes::Bytes; + use rustfs_storage_api::TransitionedObject; use std::io::Cursor; use tokio::io::AsyncReadExt; diff --git a/crates/ecstore/src/store_api.rs b/crates/ecstore/src/store_api.rs index dc1c3535d..e887a26a7 100644 --- a/crates/ecstore/src/store_api.rs +++ b/crates/ecstore/src/store_api.rs @@ -18,11 +18,7 @@ use crate::config::storageclass; use crate::error::{Error, Result}; use crate::rio::{HashReader, LimitReader}; use crate::store_utils::clean_metadata; -use crate::{ - bucket::lifecycle::bucket_lifecycle_audit::LcAuditEvent, - bucket::lifecycle::lifecycle::ExpirationOptions, - bucket::lifecycle::{bucket_lifecycle_ops::TransitionedObject, lifecycle::TransitionOptions}, -}; +use crate::{bucket::lifecycle::bucket_lifecycle_audit::LcAuditEvent, bucket::lifecycle::lifecycle::TransitionOptions}; use bytes::Bytes; use http::{HeaderMap, HeaderValue}; use rustfs_common::heal_channel::HealOpts; @@ -34,7 +30,7 @@ use rustfs_filemeta::{ use rustfs_lock::NamespaceLockWrapper; use rustfs_madmin::heal_commands::HealResultItem; use rustfs_rio::Checksum; -use rustfs_storage_api::HTTPRangeSpec; +use rustfs_storage_api::{ExpirationOptions, HTTPRangeSpec, TransitionedObject}; use rustfs_utils::CompressionAlgorithm; use rustfs_utils::http::headers::AMZ_OBJECT_TAGGING; use rustfs_utils::http::{AMZ_BUCKET_REPLICATION_STATUS, AMZ_RESTORE, AMZ_STORAGE_CLASS}; diff --git a/crates/heal/tests/common/storage_compat.rs b/crates/heal/tests/common/storage_compat.rs index ed44e47e2..45063ef4d 100644 --- a/crates/heal/tests/common/storage_compat.rs +++ b/crates/heal/tests/common/storage_compat.rs @@ -15,5 +15,25 @@ pub(crate) mod ecstore { #![allow(unused_imports)] - pub(crate) use rustfs_ecstore::{bucket, disk, endpoints, store}; + pub(crate) mod bucket { + pub(crate) mod metadata_sys { + pub(crate) use rustfs_ecstore::bucket::metadata_sys::init_bucket_metadata_sys; + } + } + + pub(crate) mod disk { + pub(crate) use rustfs_ecstore::disk::DiskStore; + + pub(crate) mod endpoint { + pub(crate) use rustfs_ecstore::disk::endpoint::Endpoint; + } + } + + pub(crate) mod endpoints { + pub(crate) use rustfs_ecstore::endpoints::{EndpointServerPools, Endpoints, PoolEndpoints}; + } + + pub(crate) mod store { + pub(crate) use rustfs_ecstore::store::{ECStore, init_local_disks}; + } } diff --git a/crates/notify/Cargo.toml b/crates/notify/Cargo.toml index 2ae40cf84..19fb5816b 100644 --- a/crates/notify/Cargo.toml +++ b/crates/notify/Cargo.toml @@ -60,6 +60,7 @@ quick-xml = { workspace = true, features = ["serialize", "serde-types", "encodin tokio = { workspace = true, features = ["test-util"] } tracing-subscriber = { workspace = true, features = ["env-filter"] } axum = { workspace = true } +rustfs-storage-api = { workspace = true } rustfs-utils = { workspace = true, features = ["path"] } serde_json = { workspace = true } time = { workspace = true } diff --git a/crates/notify/src/storage_compat.rs b/crates/notify/src/storage_compat.rs index 15f258334..36a1a7f47 100644 --- a/crates/notify/src/storage_compat.rs +++ b/crates/notify/src/storage_compat.rs @@ -79,7 +79,7 @@ impl From for NotifyObjectInfo { #[cfg(test)] mod tests { use super::*; - use rustfs_ecstore::bucket::lifecycle::bucket_lifecycle_ops::TransitionedObject; + use rustfs_storage_api::TransitionedObject; use std::{collections::HashMap, sync::Arc}; use time::{Duration, OffsetDateTime}; diff --git a/crates/policy/src/policy/action.rs b/crates/policy/src/policy/action.rs index c01fa0a30..402269fb5 100644 --- a/crates/policy/src/policy/action.rs +++ b/crates/policy/src/policy/action.rs @@ -412,10 +412,6 @@ pub enum AdminAction { TraceAdminAction, #[strum(serialize = "admin:ConsoleLog")] ConsoleLogAdminAction, - #[strum(serialize = "admin:KMSCreateKey")] - KMSCreateKeyAdminAction, - #[strum(serialize = "admin:KMSKeyStatus")] - KMSKeyStatusAdminAction, #[strum(serialize = "admin:ServerInfo")] ServerInfoAdminAction, #[strum(serialize = "admin:OBDInfo")] @@ -614,8 +610,6 @@ impl AdminAction { | AdminAction::ProfilingAdminAction | AdminAction::TraceAdminAction | AdminAction::ConsoleLogAdminAction - | AdminAction::KMSCreateKeyAdminAction - | AdminAction::KMSKeyStatusAdminAction | AdminAction::ServerInfoAdminAction | AdminAction::HealthInfoAdminAction | AdminAction::LicenseInfoAdminAction @@ -768,6 +762,13 @@ mod tests { } } + #[test] + fn test_legacy_kms_admin_actions_are_rejected() { + for raw in ["admin:KMSCreateKey", "admin:KMSKeyStatus"] { + assert!(Action::try_from(raw).is_err(), "{raw} should not parse as a policy action"); + } + } + #[test] fn test_kms_wildcard_matches_dedicated_actions() { let wildcard = Action::try_from("kms:*").expect("Should parse KMS wildcard action"); diff --git a/crates/scanner/tests/common/storage_compat.rs b/crates/scanner/tests/common/storage_compat.rs index 65737d49a..bc329e677 100644 --- a/crates/scanner/tests/common/storage_compat.rs +++ b/crates/scanner/tests/common/storage_compat.rs @@ -13,5 +13,69 @@ // limitations under the License. pub(crate) mod ecstore { - pub(crate) use rustfs_ecstore::{bucket, client, disk, endpoints, global, pools, store, tier}; + #![allow(unused_imports)] + + pub(crate) mod bucket { + pub(crate) mod lifecycle { + pub(crate) use rustfs_ecstore::bucket::lifecycle::lifecycle::TransitionOptions; + + pub(crate) mod bucket_lifecycle_ops { + pub(crate) use rustfs_ecstore::bucket::lifecycle::bucket_lifecycle_ops::{ + enqueue_transition_for_existing_objects, init_background_expiry, + }; + } + } + + pub(crate) mod metadata { + pub(crate) use rustfs_ecstore::bucket::metadata::BUCKET_LIFECYCLE_CONFIG; + } + + pub(crate) mod metadata_sys { + pub(crate) use rustfs_ecstore::bucket::metadata_sys::{get, init_bucket_metadata_sys, update}; + } + + pub(crate) mod versioning_sys { + pub(crate) use rustfs_ecstore::bucket::versioning_sys::BucketVersioningSys; + } + } + + pub(crate) mod client { + pub(crate) mod transition_api { + pub(crate) use rustfs_ecstore::client::transition_api::{ReadCloser, ReaderImpl}; + } + } + + pub(crate) mod disk { + pub(crate) use rustfs_ecstore::disk::{DiskAPI, DiskOption, STORAGE_FORMAT_FILE, new_disk}; + + pub(crate) mod endpoint { + pub(crate) use rustfs_ecstore::disk::endpoint::Endpoint; + } + } + + pub(crate) mod endpoints { + pub(crate) use rustfs_ecstore::endpoints::{EndpointServerPools, Endpoints, PoolEndpoints}; + } + + pub(crate) mod global { + pub(crate) use rustfs_ecstore::global::GLOBAL_TierConfigMgr; + } + + pub(crate) mod pools { + pub(crate) use rustfs_ecstore::pools::path2_bucket_object_with_base_path; + } + + pub(crate) mod store { + pub(crate) use rustfs_ecstore::store::{ECStore, init_local_disks}; + } + + pub(crate) mod tier { + pub(crate) mod tier_config { + pub(crate) use rustfs_ecstore::tier::tier_config::{TierConfig, TierMinIO, TierType}; + } + + pub(crate) mod warm_backend { + pub(crate) use rustfs_ecstore::tier::warm_backend::{WarmBackend, WarmBackendGetOpts, build_transition_put_options}; + } + } } diff --git a/crates/scanner/tests/lifecycle_integration_test.rs b/crates/scanner/tests/lifecycle_integration_test.rs index 1169b7db3..9e15639db 100644 --- a/crates/scanner/tests/lifecycle_integration_test.rs +++ b/crates/scanner/tests/lifecycle_integration_test.rs @@ -15,10 +15,10 @@ mod common; use crate::common::storage_compat::ecstore::{ - bucket::lifecycle::lifecycle::TransitionOptions, bucket::metadata::BUCKET_LIFECYCLE_CONFIG, bucket::{ - lifecycle::bucket_lifecycle_ops::enqueue_transition_for_existing_objects, metadata_sys, + lifecycle::{TransitionOptions, bucket_lifecycle_ops::enqueue_transition_for_existing_objects}, + metadata_sys, versioning_sys::BucketVersioningSys, }, client::transition_api::{ReadCloser, ReaderImpl}, diff --git a/crates/storage-api/src/lib.rs b/crates/storage-api/src/lib.rs index 8c786b48a..f625de46a 100644 --- a/crates/storage-api/src/lib.rs +++ b/crates/storage-api/src/lib.rs @@ -25,6 +25,7 @@ pub use bucket::{BucketInfo, BucketOperations, BucketOptions, DeleteBucketOption pub use error::{StorageErrorCode, StorageResult}; pub use multipart::{CompletePart, ListMultipartsInfo, ListPartsInfo, MultipartInfo, MultipartUploadResult, PartInfo}; pub use object::{DeletedObject, ObjectToDelete}; +pub use object::{ExpirationOptions, TransitionedObject}; pub use object::{HTTPPreconditions, HTTPRangeError, HTTPRangeSpec, ObjectLockRetentionOptions}; pub use object::{HealOperations, MultipartOperations, NamespaceLocking, ObjectIO, ObjectOperations}; pub use object::{ListObjectVersionsInfo, ListObjectsInfo, ListObjectsV2Info, ListOperations, ObjectInfoOrErr}; diff --git a/crates/storage-api/src/object.rs b/crates/storage-api/src/object.rs index 80ddbc058..074ce6f54 100644 --- a/crates/storage-api/src/object.rs +++ b/crates/storage-api/src/object.rs @@ -48,6 +48,20 @@ pub struct ObjectLockRetentionOptions { pub bypass_governance: bool, } +#[derive(Debug, Clone, Default)] +pub struct ExpirationOptions { + pub expire: bool, +} + +#[derive(Debug, Default, Clone)] +pub struct TransitionedObject { + pub name: String, + pub version_id: String, + pub tier: String, + pub free_version: bool, + pub status: String, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ObjectPreconditionPart { pub number: usize, @@ -797,6 +811,19 @@ mod tests { assert_eq!(object.delete_marker_replication_status(), ReplicationStatusType::Empty); } + #[test] + fn lifecycle_helper_defaults_preserve_existing_contracts() { + let expiration = ExpirationOptions::default(); + let transitioned = TransitionedObject::default(); + + assert!(!expiration.expire); + assert!(transitioned.name.is_empty()); + assert!(transitioned.version_id.is_empty()); + assert!(transitioned.tier.is_empty()); + assert!(!transitioned.free_version); + assert!(transitioned.status.is_empty()); + } + #[derive(Debug)] struct TestListBackend; diff --git a/docs/architecture/admin-route-action-snapshot.md b/docs/architecture/admin-route-action-snapshot.md index 2a57f6d9a..54d4bbf76 100644 --- a/docs/architecture/admin-route-action-snapshot.md +++ b/docs/architecture/admin-route-action-snapshot.md @@ -103,9 +103,9 @@ through router canonicalization unless the row explicitly says otherwise. | Site replication | `PUT /v3/site-replication/add`; `PUT /v3/site-replication/remove`; `GET /v3/site-replication/info`; `GET /v3/site-replication/metainfo`; `GET /v3/site-replication/status`; `POST /v3/site-replication/devnull`; `POST /v3/site-replication/netperf`; `PUT /v3/site-replication/edit`; `PUT /v3/site-replication/peer/join`; `PUT /v3/site-replication/peer/bucket-ops`; `PUT /v3/site-replication/peer/iam-item`; `PUT /v3/site-replication/peer/bucket-meta`; `GET /v3/site-replication/peer/idp-settings`; `PUT /v3/site-replication/peer/edit`; `PUT /v3/site-replication/peer/remove`; `PUT /v3/site-replication/resync/op`; `PUT /v3/site-replication/state/edit` | `site_replication.rs` | add/remove/info/operation/resync actions selected per handler | | Admin profiling | `GET /rustfs/admin/debug/pprof/profile`; `GET /rustfs/admin/debug/pprof/status` | `profile_admin.rs`, `profile.rs` | `ProfilingAdminAction` | | TLS debug | `GET /rustfs/admin/debug/tls/status` | `tls_debug.rs`, `profile.rs` | `ProfilingAdminAction` via shared profile authorization | -| KMS legacy management | `POST /v3/kms/create-key`; `POST /v3/kms/key/create`; `GET /v3/kms/describe-key`; `GET /v3/kms/key/status`; `GET /v3/kms/list-keys`; `POST /v3/kms/generate-data-key`; `GET|POST /v3/kms/status`; `GET /v3/kms/config`; `POST /v3/kms/clear-cache` | `kms_management.rs`, `kms_keys.rs` | create/status/server-info KMS admin actions as checked per handler | +| KMS legacy management | `POST /v3/kms/create-key`; `POST /v3/kms/key/create`; `GET /v3/kms/describe-key`; `GET /v3/kms/key/status`; `GET /v3/kms/list-keys`; `POST /v3/kms/generate-data-key`; `GET|POST /v3/kms/status`; `GET /v3/kms/config`; `POST /v3/kms/clear-cache` | `kms_management.rs`, `kms_keys.rs` | dedicated `kms:*` actions for key create/status/list/data-key/cache paths; `ServerInfoAdminAction` where preserved by handler | | KMS dynamic control | `POST /v3/kms/configure`; `POST /v3/kms/start`; `POST /v3/kms/stop`; `GET /v3/kms/service-status`; `POST /v3/kms/reconfigure` | `kms_dynamic.rs` | `ServerInfoAdminAction` | -| KMS keys | `POST /v3/kms/keys`; `DELETE /v3/kms/keys/delete`; `POST /v3/kms/keys/cancel-deletion`; `GET /v3/kms/keys`; `GET /v3/kms/keys/{key_id}` | `kms_keys.rs` | `KMSCreateKeyAdminAction`, `KMSKeyStatusAdminAction`, `ServerInfoAdminAction` per handler | +| KMS keys | `POST /v3/kms/keys`; `DELETE /v3/kms/keys/delete`; `POST /v3/kms/keys/cancel-deletion`; `GET /v3/kms/keys`; `GET /v3/kms/keys/{key_id}` | `kms_keys.rs` | dedicated `kms:*` actions per handler | | OIDC public | `GET /v3/oidc/providers`; `GET /v3/oidc/authorize/{provider_id}`; `GET /v3/oidc/callback/{provider_id}`; `GET /v3/oidc/logout` | `oidc.rs` | Public OIDC exception in `is_oidc_path` | | OIDC config | `GET /v3/oidc/config`; `PUT|DELETE /v3/oidc/config/{provider_id}`; `POST /v3/oidc/validate` | `oidc.rs` | `ServerInfoAdminAction` for read/validate; `ConfigUpdateAdminAction` for mutation | diff --git a/docs/architecture/compat-cleanup-register.md b/docs/architecture/compat-cleanup-register.md index 54b3e5d15..26d8e51d0 100644 --- a/docs/architecture/compat-cleanup-register.md +++ b/docs/architecture/compat-cleanup-register.md @@ -12,12 +12,7 @@ for later deletion. ## Open Items -- `RUSTFS_COMPAT_TODO(S-012)` - - Task: `S-012` - - File: `rustfs/src/admin/handlers/kms_keys.rs` - - Why: legacy KMS create-key and key-status admin grants must keep working during the dedicated KMS policy migration. - - Removal condition: remove after KMS admin clients and built-in policies use `kms:Configure`, `kms:DescribeKey`, and `kms:ListKeys`. - - Status: planned cleanup. +No compatibility code is currently registered. ## Review Checklist diff --git a/docs/architecture/crate-boundaries.md b/docs/architecture/crate-boundaries.md index 0db27c91d..03b9e2b21 100644 --- a/docs/architecture/crate-boundaries.md +++ b/docs/architecture/crate-boundaries.md @@ -95,6 +95,7 @@ Required `rustfs-storage-api` public re-exports: - `pub use error::{StorageErrorCode, StorageResult};` - `pub use multipart::{CompletePart, ListMultipartsInfo, ListPartsInfo, MultipartInfo, MultipartUploadResult, PartInfo};` - `pub use object::{HTTPPreconditions, HTTPRangeError, HTTPRangeSpec, ObjectLockRetentionOptions};` +- `pub use object::{ExpirationOptions, TransitionedObject};` - `pub use object::{HealOperations, MultipartOperations, NamespaceLocking, ObjectIO, ObjectOperations};` - `pub use object::{ListObjectVersionsInfo, ListObjectsInfo, ListObjectsV2Info, ListOperations, ObjectInfoOrErr};` - `pub use object::{ObjectPreconditionError, ObjectPreconditionPart, ObjectPreconditionState};` @@ -117,3 +118,7 @@ directly for `ObjectIO`, `ObjectOperations`, `ListOperations`, `MultipartOperations`, `HealOperations`, and `NamespaceLocking`; ECStore keeps the concrete compatibility traits only for internal implementation and downstream compatibility. + +ECStore internal consumers must use `rustfs-storage-api` lifecycle helper DTOs +directly for `ExpirationOptions` and `TransitionedObject`; ECStore keeps the +old lifecycle paths only as downstream compatibility re-exports. diff --git a/docs/architecture/migration-progress.md b/docs/architecture/migration-progress.md index 31b93c73f..cd2fb9ef2 100644 --- a/docs/architecture/migration-progress.md +++ b/docs/architecture/migration-progress.md @@ -5,18 +5,17 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block ## Current Context - Issue: [`rustfs/backlog#660`](https://github.com/rustfs/backlog/issues/660) -- Branch: `overtrue/arch-delete-object-contracts` -- Baseline: stacked on `rustfs/rustfs#3579` head - (`903aff047d2faffaec907637d64440c84053f62e`). -- PR type for this branch: `consumer-migration` +- Branch: `overtrue/arch-storage-api-lifecycle-contracts` +- Baseline: stacked on `rustfs/rustfs#3594` head + (`6fc05b84e2cd22a0482adfbc042184b03f8fdfa6`). +- PR type for this branch: `pure-move` - Runtime behavior changes: no migration behavior change expected. -- Rust code changes: move delete-object DTO contracts from ECStore store_api to - rustfs-storage-api, keep ECStore old-path type aliases, and migrate RustFS, - scanner, and ECStore internal consumers to the storage-api contracts. -- CI/script changes: add a migration guard rejecting reintroduced ECStore - delete DTO definitions, public re-exports, or internal old-path consumers. -- Docs changes: record the delete-object contract move and consumer cleanup - slices. +- Rust code changes: move lifecycle helper DTO contracts for expiration and + transitioned object metadata into rustfs-storage-api, switch ECStore internal + consumers to direct storage-api imports, and keep ECStore old-path re-exports. +- CI/script changes: add migration guards rejecting reintroduced ECStore + lifecycle helper DTO definitions and old internal consumer imports. +- Docs changes: record the lifecycle helper pure-move slice. ## Phase 0 Tasks @@ -304,6 +303,22 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block and persisted config serialization still writes the original secret values. - Verification: focused KMS redaction/status tests, full KMS tests, migration guards, Rust quality scan, clippy, and `make pre-commit` passed. +- [x] `S-014` Remove legacy KMS admin action fallbacks. + - Acceptance: KMS create, describe, and list-key handlers authorize only the + dedicated `kms:*` actions and no longer retain legacy admin grant fallbacks. + - Must preserve: legacy KMS endpoint URLs, query aliases, request bodies, and + response contracts remain unchanged. + - Verification: focused KMS auth and route-policy tests, migration guards, + formatting, diff hygiene, risk scan, full pre-commit, and required + three-expert review passed before push. +- [x] `S-015` Remove legacy KMS admin policy action taxonomy. + - Acceptance: `admin:KMSCreateKey` and `admin:KMSKeyStatus` no longer parse as + valid policy actions; KMS key handlers keep using dedicated `kms:*` actions. + - Must preserve: legacy KMS endpoint URLs, query aliases, request bodies, and + response contracts remain unchanged. + - Verification: focused policy and KMS auth tests, route-policy tests, + migration guards, formatting, diff hygiene, risk scan, full pre-commit, and + required three-expert review passed before push. - [x] `KMSD-001` Inventory KMS development defaults. - Acceptance: [`kms-development-defaults-inventory.md`](kms-development-defaults-inventory.md) @@ -1240,6 +1255,47 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block formatting check, diff hygiene, risk scan, full pre-commit, and required three-expert review passed before push. +- [x] `API-049` Remove test and fuzz ECStore module passthroughs. + - Current branch: `overtrue/arch-test-fuzz-compat-boundaries`. + - Current slice: replace the remaining e2e, heal-test, scanner-test, and + fuzz-target ECStore module passthroughs with explicit local compatibility + aliases, split fuzz storage compatibility by target, and empty the + passthrough guard snapshot. + - Acceptance: no `storage_compat.rs` file may expose broad + `rustfs_ecstore` module passthroughs; the migration guard now rejects any + new passthrough unless a later slice deliberately adds a reviewed + allowlist entry. + - Must preserve: e2e bucket target and RPC helper imports, heal test disk and + store setup imports, scanner test lifecycle/tier/disk/storage imports, + fuzz bucket validation behavior, and fuzz path containment behavior. + - Risk defense: this is test-harness and fuzz-harness import ownership + cleanup only; ECStore remains the owner of the same concrete APIs and no + production runtime path is changed. + - Verification: focused test/fuzz compiles, migration and layer guards, + formatting check, diff hygiene, risk scan, full pre-commit, and required + three-expert review passed before push. + +- [x] `API-050` Move lifecycle helper DTO contracts. + - Current branch: `overtrue/arch-storage-api-lifecycle-contracts`. + - Current slice: move `ExpirationOptions` and `TransitionedObject` into + rustfs-storage-api, update ECStore internal consumers plus notify test + coverage to import them directly, and keep ECStore old-path re-exports for + downstream compatibility callers. + - Acceptance: rustfs-storage-api exports both lifecycle helper DTOs, ECStore + no longer owns their concrete struct definitions, ECStore internal + consumers and notify coverage use the storage-api contracts directly, old + ECStore lifecycle paths remain available as re-exports, and migration rules + reject restoring the ECStore definitions or old internal imports. + - Must preserve: lifecycle expiration flags, transitioned object journal + metadata, object info construction, notify event conversion, and all old + ECStore import paths used by existing callers. + - Risk defense: this is a pure DTO move; no lifecycle scheduling, object I/O, + transition journal, replication, or reader behavior is changed. + - Verification: storage-api lifecycle helper unit test, ECStore transitioned + lifecycle tests, notify event conversion test, focused compile checks, + migration and layer guards, formatting check, diff hygiene, risk scan, full + pre-commit, and required three-expert review passed before push. + ## Phase 8 Background Controller Tasks - [x] `BGC-001` Inventory background services. @@ -1510,20 +1566,85 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block 1. `pure-move`/`consumer-migration`: continue larger cleanup slices with the loss-prevention guards active for remaining ECStore compatibility contracts - outside the production compatibility boundaries already cleaned. + now that broad compatibility passthroughs are fully closed. ## Pre-Push Review Log | Expert | Status | Notes | |---|---|---| -| Quality/architecture | passed | API-042/API-043/API-044/API-045/API-046/API-047/API-048 narrow notify, S3 Select, OBS, IAM, Swift, heal, scanner, and RustFS runtime compatibility contracts without moving ECStore storage metadata ownership. | -| Migration preservation | passed | Event builder call sites, ECStore event bridge conversion, restore event data, version IDs, metadata filtering, config read/save semantics, S3 Select store/error/buffer semantics, OBS metrics state reads, IAM config/notification/error semantics, Swift bucket metadata access, heal disk/resume/task behavior, scanner lifecycle/replication/data-usage behavior, RustFS startup/admin/app/storage runtime access, unchanged no-op handling, and remove-event behavior are preserved. | +| Quality/architecture | passed | S-015 removes obsolete KMS admin policy action variants after the handler fallback cleanup; API-042/API-043/API-044/API-045/API-046/API-047/API-048/API-049/API-050 narrow notify, S3 Select, OBS, IAM, Swift, heal, scanner, RustFS runtime, test, fuzz, and lifecycle helper compatibility contracts without moving ECStore storage metadata ownership. | +| Migration preservation | passed | KMS endpoint URLs, query aliases, request bodies, response contracts, and dedicated `kms:*` authorization behavior are preserved; event builder call sites, ECStore event bridge conversion, restore event data, version IDs, metadata filtering, config read/save semantics, S3 Select store/error/buffer semantics, OBS metrics state reads, IAM config/notification/error semantics, Swift bucket metadata access, heal disk/resume/task behavior, scanner lifecycle/replication/data-usage behavior, RustFS startup/admin/app/storage runtime access, e2e/test/fuzz import behavior, lifecycle expiration/transition helper DTO field contracts, unchanged no-op handling, and remove-event behavior are preserved. | | Testing/verification | passed | Focused compiles/tests, guards, formatting, diff hygiene, risk scan, and full `make pre-commit` passed for the current slice. | ## Verification Notes Passed before push: +- API-050 current slice: + - `cargo test -p rustfs-storage-api lifecycle_helper_defaults_preserve_existing_contracts --no-fail-fast`: + passed. + - `cargo check --tests -p rustfs-storage-api -p rustfs-ecstore -p rustfs-notify`: + passed. + - `cargo test -p rustfs-ecstore transitioned --no-fail-fast`: passed. + - `cargo test -p rustfs-notify ecstore_object_info_conversion_preserves_notify_event_fields --no-fail-fast`: + passed. + - `cargo check --tests -p rustfs`: passed. + - `./scripts/check_architecture_migration_rules.sh`: passed. + - `./scripts/check_layer_dependencies.sh`: passed. + - `cargo fmt --all --check`: passed. + - `git diff --check`: passed. + - Rust risk scan: passed; no new unwrap/expect, panic/todo/unsafe, risky + casts, ad-hoc error construction, or sensitive-token handling in added + lines. + - `make pre-commit`: passed. + +- S-015 current slice: + - `cargo test -p rustfs-policy test_legacy_kms_admin_actions_are_rejected --no-fail-fast`: + passed. + - `cargo test -p rustfs kms_key_auth_actions_use_dedicated_kms_actions --no-fail-fast`: + passed. + - `cargo test -p rustfs route_policy_records_dedicated_kms_actions --no-fail-fast`: + passed. + - `cargo test -p rustfs route_policy_rejects_server_info_for_sensitive_kms_actions --no-fail-fast`: + passed. + - `cargo check --tests -p rustfs-policy -p rustfs`: passed. + - `./scripts/check_architecture_migration_rules.sh`: passed. + - `./scripts/check_layer_dependencies.sh`: passed. + - `cargo fmt --all --check`: passed. + - `git diff --check`: passed. + - `make pre-commit`: passed. + +- S-014 previous slice: + - `cargo test -p rustfs kms_key_auth_actions_use_dedicated_kms_actions --no-fail-fast`: + passed. + - `cargo test -p rustfs route_policy_records_dedicated_kms_actions --no-fail-fast`: + passed. + - `cargo test -p rustfs route_policy_rejects_server_info_for_sensitive_kms_actions --no-fail-fast`: + passed. + - `cargo check --tests -p rustfs`: passed. + - `./scripts/check_architecture_migration_rules.sh`: passed. + - `./scripts/check_layer_dependencies.sh`: passed. + - `cargo fmt --all --check`: passed. + - `git diff --check`: passed. + - Source marker scan: passed; no non-doc `RUSTFS_COMPAT_TODO` markers remain. + - Rust risk scan: passed; no new unwrap/expect, panic/todo/unsafe, risky + casts, ad-hoc error construction, or sensitive-token handling in added + lines. + - `make pre-commit`: passed. + +- API-049 current slice: + - `cargo check --tests -p rustfs-heal -p rustfs-scanner -p e2e_test`: + passed. + - `cargo check --manifest-path fuzz/Cargo.toml --all-targets`: passed. + - `./scripts/check_architecture_migration_rules.sh`: passed. + - `./scripts/check_layer_dependencies.sh`: passed. + - `cargo fmt --all --check`: passed. + - `git diff --check`: passed. + - Rust risk scan: passed; no new unwrap/expect, panic/todo/unsafe, risky + casts, ad-hoc error construction, or sensitive-token handling in added + lines. + - `make pre-commit`: passed. + - API-048 current slice: - `cargo check --tests -p rustfs`: passed. - `./scripts/check_architecture_migration_rules.sh`: passed. diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index d0c2c4184..115d90f30 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -8758,6 +8758,7 @@ name = "rustfs-storage-api" version = "1.0.0-beta.8" dependencies = [ "async-trait", + "rustfs-filemeta", "serde", "time", "uuid", diff --git a/fuzz/fuzz_targets/bucket_validation.rs b/fuzz/fuzz_targets/bucket_validation.rs index 2f528aa58..c7d62fb99 100644 --- a/fuzz/fuzz_targets/bucket_validation.rs +++ b/fuzz/fuzz_targets/bucket_validation.rs @@ -1,9 +1,12 @@ #![no_main] +#[path = "bucket_validation/storage_compat.rs"] mod storage_compat; use libfuzzer_sys::fuzz_target; -use crate::storage_compat::ecstore::bucket::utils::{check_bucket_and_object_names, check_list_objs_args, check_valid_bucket_name_strict}; +use crate::storage_compat::ecstore::bucket::utils::{ + check_bucket_and_object_names, check_list_objs_args, check_valid_bucket_name_strict, is_meta_bucketname, +}; fn parse_case(data: &[u8]) -> (String, String) { let text = String::from_utf8_lossy(data); @@ -25,6 +28,7 @@ fuzz_target!(|data: &[u8]| { let (bucket, object) = parse_case(data); let strict_bucket_ok = check_valid_bucket_name_strict(&bucket).is_ok(); + let valid_bucket_for_object_ops = strict_bucket_ok || is_meta_bucketname(&bucket); let pair_ok = check_bucket_and_object_names(&bucket, &object).is_ok(); let list_ok = check_list_objs_args(&bucket, &object, &None).is_ok(); @@ -48,10 +52,16 @@ fuzz_target!(|data: &[u8]| { } if pair_ok { - assert!(strict_bucket_ok, "accepted bucket/object pair must also satisfy strict bucket validation"); + assert!( + valid_bucket_for_object_ops, + "accepted bucket/object pair must satisfy strict bucket validation or meta bucket compatibility" + ); } if list_ok { - assert!(strict_bucket_ok, "accepted list-object args must also satisfy strict bucket validation"); + assert!( + valid_bucket_for_object_ops, + "accepted list-object args must satisfy strict bucket validation or meta bucket compatibility" + ); } }); diff --git a/fuzz/fuzz_targets/bucket_validation/storage_compat.rs b/fuzz/fuzz_targets/bucket_validation/storage_compat.rs new file mode 100644 index 000000000..e34daca66 --- /dev/null +++ b/fuzz/fuzz_targets/bucket_validation/storage_compat.rs @@ -0,0 +1,23 @@ +// Copyright 2024 RustFS Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub(crate) mod ecstore { + pub(crate) mod bucket { + pub(crate) mod utils { + pub(crate) use rustfs_ecstore::bucket::utils::{ + check_bucket_and_object_names, check_list_objs_args, check_valid_bucket_name_strict, is_meta_bucketname, + }; + } + } +} diff --git a/fuzz/fuzz_targets/path_containment.rs b/fuzz/fuzz_targets/path_containment.rs index 4bc3678e9..071e485c1 100644 --- a/fuzz/fuzz_targets/path_containment.rs +++ b/fuzz/fuzz_targets/path_containment.rs @@ -1,5 +1,6 @@ #![no_main] +#[path = "path_containment/storage_compat.rs"] mod storage_compat; use libfuzzer_sys::fuzz_target; diff --git a/fuzz/fuzz_targets/storage_compat.rs b/fuzz/fuzz_targets/path_containment/storage_compat.rs similarity index 70% rename from fuzz/fuzz_targets/storage_compat.rs rename to fuzz/fuzz_targets/path_containment/storage_compat.rs index 3d19f6c4a..ce2e32bb7 100644 --- a/fuzz/fuzz_targets/storage_compat.rs +++ b/fuzz/fuzz_targets/path_containment/storage_compat.rs @@ -13,5 +13,11 @@ // limitations under the License. pub(crate) mod ecstore { - pub(crate) use rustfs_ecstore::bucket; + pub(crate) mod bucket { + pub(crate) mod utils { + pub(crate) use rustfs_ecstore::bucket::utils::{ + check_object_name_for_length_and_slash, has_bad_path_component, is_valid_object_prefix, + }; + } + } } diff --git a/rustfs/src/admin/handlers/kms_keys.rs b/rustfs/src/admin/handlers/kms_keys.rs index 0b01325c8..fb642b7bf 100644 --- a/rustfs/src/admin/handlers/kms_keys.rs +++ b/rustfs/src/admin/handlers/kms_keys.rs @@ -24,7 +24,7 @@ use hyper::{HeaderMap, Method, StatusCode}; use matchit::Params; use rustfs_config::MAX_ADMIN_REQUEST_BODY_SIZE; use rustfs_kms::{KmsError, init_global_kms_service_manager, types::*}; -use rustfs_policy::policy::action::{Action, AdminAction, KmsAction}; +use rustfs_policy::policy::action::{Action, KmsAction}; use s3s::header::CONTENT_TYPE; use s3s::{Body, S3Request, S3Response, S3Result, s3_error}; use serde::{Deserialize, Serialize}; @@ -126,27 +126,15 @@ async fn kms_encryption_service_from_context() -> Option Vec { - // RUSTFS_COMPAT_TODO(S-012): keep legacy KMS create-key grants during KMS policy migration. Remove after KMS admin clients and built-in policies use kms:Configure. - vec![ - Action::KmsAction(KmsAction::ConfigureAction), - Action::AdminAction(AdminAction::KMSCreateKeyAdminAction), - ] + vec![Action::KmsAction(KmsAction::ConfigureAction)] } fn kms_describe_key_actions() -> Vec { - // RUSTFS_COMPAT_TODO(S-012): keep legacy KMS key-status grants during KMS policy migration. Remove after KMS admin clients and built-in policies use kms:DescribeKey. - vec![ - Action::KmsAction(KmsAction::DescribeKeyAction), - Action::AdminAction(AdminAction::KMSKeyStatusAdminAction), - ] + vec![Action::KmsAction(KmsAction::DescribeKeyAction)] } fn kms_list_keys_actions() -> Vec { - // RUSTFS_COMPAT_TODO(S-012): keep legacy KMS key-status grants during KMS policy migration. Remove after KMS admin clients and built-in policies use kms:ListKeys. - vec![ - Action::KmsAction(KmsAction::ListKeysAction), - Action::AdminAction(AdminAction::KMSKeyStatusAdminAction), - ] + vec![Action::KmsAction(KmsAction::ListKeysAction)] } fn kms_generate_data_key_actions() -> Vec { @@ -403,17 +391,9 @@ mod tests { #[test] fn kms_key_auth_actions_use_dedicated_kms_actions() { - let create_actions = kms_create_key_actions(); - assert_has_action(&create_actions, Action::KmsAction(KmsAction::ConfigureAction)); - assert_has_action(&create_actions, Action::AdminAction(AdminAction::KMSCreateKeyAdminAction)); - - let describe_actions = kms_describe_key_actions(); - assert_has_action(&describe_actions, Action::KmsAction(KmsAction::DescribeKeyAction)); - assert_has_action(&describe_actions, Action::AdminAction(AdminAction::KMSKeyStatusAdminAction)); - - let list_actions = kms_list_keys_actions(); - assert_has_action(&list_actions, Action::KmsAction(KmsAction::ListKeysAction)); - assert_has_action(&list_actions, Action::AdminAction(AdminAction::KMSKeyStatusAdminAction)); + assert_eq!(kms_create_key_actions(), vec![Action::KmsAction(KmsAction::ConfigureAction)]); + assert_eq!(kms_describe_key_actions(), vec![Action::KmsAction(KmsAction::DescribeKeyAction)]); + assert_eq!(kms_list_keys_actions(), vec![Action::KmsAction(KmsAction::ListKeysAction)]); } #[test] diff --git a/scripts/check_architecture_migration_rules.sh b/scripts/check_architecture_migration_rules.sh index c7a464ec8..964f870ce 100755 --- a/scripts/check_architecture_migration_rules.sh +++ b/scripts/check_architecture_migration_rules.sh @@ -59,6 +59,8 @@ STORE_API_LIST_HELPER_REEXPORTS_FILE="${TMP_DIR}/store_api_list_helper_reexports STORE_API_LIST_RESPONSE_REEXPORTS_FILE="${TMP_DIR}/store_api_list_response_reexports.txt" STORE_API_DELETE_DTO_REEXPORTS_FILE="${TMP_DIR}/store_api_delete_dto_reexports.txt" STORE_API_DELETE_DTO_INTERNAL_HITS_FILE="${TMP_DIR}/store_api_delete_dto_internal_hits.txt" +STORE_API_LIFECYCLE_HELPER_DEFINITION_HITS_FILE="${TMP_DIR}/store_api_lifecycle_helper_definition_hits.txt" +STORE_API_LIFECYCLE_HELPER_OLD_CONSUMER_HITS_FILE="${TMP_DIR}/store_api_lifecycle_helper_old_consumer_hits.txt" STORE_API_EXTERNAL_LIST_CONSUMER_HITS_FILE="${TMP_DIR}/store_api_external_list_consumer_hits.txt" STORE_API_EXTERNAL_OPERATION_CONSUMER_HITS_FILE="${TMP_DIR}/store_api_external_operation_consumer_hits.txt" STORE_API_OBJECT_OPERATION_LOCAL_METHOD_HITS_FILE="${TMP_DIR}/store_api_object_operation_local_method_hits.txt" @@ -214,6 +216,10 @@ require_source_line \ "crates/storage-api/src/lib.rs" \ "pub use object::{DeletedObject, ObjectToDelete};" \ "storage-api public delete object contract re-export" +require_source_line \ + "crates/storage-api/src/lib.rs" \ + "pub use object::{ExpirationOptions, TransitionedObject};" \ + "storage-api public lifecycle helper contract re-export" require_source_line \ "crates/storage-api/src/lib.rs" \ "pub use object::{ListObjectVersionsInfo, ListObjectsInfo, ListObjectsV2Info, ListOperations, ObjectInfoOrErr};" \ @@ -334,6 +340,36 @@ if [[ -s "$STORE_API_DELETE_DTO_INTERNAL_HITS_FILE" ]]; then report_failure "ecstore internal delete DTO old store_api path reintroduced: $(paste -sd '; ' "$STORE_API_DELETE_DTO_INTERNAL_HITS_FILE")" fi +( + cd "$ROOT_DIR" + rg -n --no-heading 'pub struct (?:ExpirationOptions|TransitionedObject)\b' \ + crates/ecstore/src/bucket/lifecycle/core.rs \ + crates/ecstore/src/bucket/lifecycle/bucket_lifecycle_ops.rs || true +) >"$STORE_API_LIFECYCLE_HELPER_DEFINITION_HITS_FILE" + +if [[ -s "$STORE_API_LIFECYCLE_HELPER_DEFINITION_HITS_FILE" ]]; then + report_failure "ECStore lifecycle helper DTO definitions must stay in rustfs-storage-api: $(paste -sd '; ' "$STORE_API_LIFECYCLE_HELPER_DEFINITION_HITS_FILE")" +fi + +require_source_line \ + "crates/ecstore/src/bucket/lifecycle/core.rs" \ + "pub use rustfs_storage_api::ExpirationOptions;" \ + "ECStore ExpirationOptions compatibility re-export" +require_source_line \ + "crates/ecstore/src/bucket/lifecycle/bucket_lifecycle_ops.rs" \ + "pub use rustfs_storage_api::TransitionedObject;" \ + "ECStore TransitionedObject compatibility re-export" + +( + cd "$ROOT_DIR" + rg -n --no-heading 'crate::bucket::lifecycle::(?:bucket_lifecycle_ops::TransitionedObject|core::ExpirationOptions|lifecycle::ExpirationOptions)|use crate::bucket::lifecycle::(?:bucket_lifecycle_ops::TransitionedObject|core::ExpirationOptions|lifecycle::ExpirationOptions)|rustfs_ecstore::bucket::lifecycle::bucket_lifecycle_ops::TransitionedObject' \ + crates/ecstore/src rustfs/src crates/scanner/src crates/scanner/tests crates/notify/src --glob '*.rs' || true +) >"$STORE_API_LIFECYCLE_HELPER_OLD_CONSUMER_HITS_FILE" + +if [[ -s "$STORE_API_LIFECYCLE_HELPER_OLD_CONSUMER_HITS_FILE" ]]; then + report_failure "lifecycle helper DTO consumers must import rustfs-storage-api directly: $(paste -sd '; ' "$STORE_API_LIFECYCLE_HELPER_OLD_CONSUMER_HITS_FILE")" +fi + ( cd "$ROOT_DIR" rg -n --no-heading 'rustfs_ecstore::store_api(?:::\{[^}]*\b(?:ListObjectVersionsInfo|ListObjectsV2Info|ObjectInfoOrErr)\b|::(?:ListObjectVersionsInfo|ListObjectsV2Info|ObjectInfoOrErr)\b)' \ @@ -424,22 +460,6 @@ if [[ -s "$UNAPPROVED_STORE_API_COMPAT_ALIAS_HITS_FILE" ]]; then fi cat >"$ECSTORE_COMPAT_PASSTHROUGH_EXPECTED_FILE" <<'EOF' -crates/e2e_test/src/storage_compat.rs:bucket -crates/e2e_test/src/storage_compat.rs:disk -crates/e2e_test/src/storage_compat.rs:rpc -crates/heal/tests/common/storage_compat.rs:bucket -crates/heal/tests/common/storage_compat.rs:disk -crates/heal/tests/common/storage_compat.rs:endpoints -crates/heal/tests/common/storage_compat.rs:store -crates/scanner/tests/common/storage_compat.rs:bucket -crates/scanner/tests/common/storage_compat.rs:client -crates/scanner/tests/common/storage_compat.rs:disk -crates/scanner/tests/common/storage_compat.rs:endpoints -crates/scanner/tests/common/storage_compat.rs:global -crates/scanner/tests/common/storage_compat.rs:pools -crates/scanner/tests/common/storage_compat.rs:store -crates/scanner/tests/common/storage_compat.rs:tier -fuzz/fuzz_targets/storage_compat.rs:bucket EOF sort -o "$ECSTORE_COMPAT_PASSTHROUGH_EXPECTED_FILE" "$ECSTORE_COMPAT_PASSTHROUGH_EXPECTED_FILE"