mirror of
https://github.com/rustfs/rustfs.git
synced 2026-10-27 14:27:10 +00:00
test(ecstore): pin the persisted bucket-config XML bytes for 13 families (#8408)
Before any DTO or codec is replaced, pin the exact bytes that deserialize followed by serialize produces for every persisted bucket-configuration XML family in its three source shapes (AWS documentation, MinIO-written, RustFS-written), and the bytes RustFS writes today for a typed value. Byte equality only: a structural comparison would let a codec replacement change the persisted encoding unnoticed. The generator script refuses to overwrite existing goldens without --force, refuses on dirty codec paths, cannot report success from its write mode, and records the generating commit in the fixtures README. Refs rustfs/backlog#2744, rustfs/backlog#2734
This commit is contained in:
@@ -0,0 +1,495 @@
|
||||
// Copyright 2026 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.
|
||||
|
||||
//! Byte-level goldens for the 13 persisted bucket-configuration XML families
|
||||
//! (rustfs/backlog#2744).
|
||||
//!
|
||||
//! Responsible for: pinning the exact bytes that `deserialize` followed by
|
||||
//! `serialize` produces for every family in its three source shapes (AWS
|
||||
//! documentation shape, MinIO-written shape, RustFS-written shape), and the
|
||||
//! exact bytes RustFS writes for a typed value. Only bytes are compared; a
|
||||
//! structural comparison would let a codec replacement change the persisted
|
||||
//! encoding without failing here.
|
||||
//!
|
||||
//! Not responsible for: validating configurations, touching the bucket
|
||||
//! metadata store, or choosing a canonical shape.
|
||||
//!
|
||||
//! Upstream: `rustfs_ecstore::api::bucket::utils::{deserialize, serialize}`
|
||||
//! and the s3s DTOs they are generic over. Downstream: the DTO/codec
|
||||
//! replacement (T1.4) and the persisted-bytes comparison (T3.7), which must
|
||||
//! keep every golden green without regenerating it. Regeneration is reserved
|
||||
//! to `scripts/gen_bucket_config_goldens.sh`, which sets
|
||||
//! `BUCKET_CONFIG_GOLDENS_WRITE`: in that mode every test writes its files
|
||||
//! and then fails on purpose, so a write run never prints a green line.
|
||||
|
||||
use rustfs_ecstore::api::bucket::utils::{deserialize, serialize};
|
||||
use s3s::dto::{
|
||||
AbortIncompleteMultipartUpload, AccelerateConfiguration, BucketAccelerateStatus, BucketLifecycleConfiguration,
|
||||
BucketLoggingStatus, BucketVersioningStatus, CORSConfiguration, CORSRule, Condition, DefaultRetention, DelMarkerExpiration,
|
||||
DeleteMarkerReplication, DeleteMarkerReplicationStatus, DeleteReplication, DeleteReplicationStatus, Destination,
|
||||
ErrorDocument, Event, ExcludedPrefix, ExistingObjectReplication, ExistingObjectReplicationStatus, ExpirationStatus,
|
||||
FilterRule, FilterRuleName, IndexDocument, LifecycleExpiration, LifecycleRule, LifecycleRuleAndOperator, LifecycleRuleFilter,
|
||||
LoggingEnabled, NoncurrentVersionExpiration, NotificationConfiguration, NotificationConfigurationFilter,
|
||||
ObjectLockConfiguration, ObjectLockEnabled, ObjectLockRetentionMode, ObjectLockRule, Payer, PublicAccessBlockConfiguration,
|
||||
QueueConfiguration, Redirect, ReplicaModifications, ReplicaModificationsStatus, ReplicationConfiguration, ReplicationRule,
|
||||
ReplicationRuleAndOperator, ReplicationRuleFilter, ReplicationRuleStatus, RequestPaymentConfiguration, RoutingRule,
|
||||
S3KeyFilter, ServerSideEncryption, ServerSideEncryptionByDefault, ServerSideEncryptionConfiguration,
|
||||
ServerSideEncryptionRule, SourceSelectionCriteria, StorageClass, Tag, Tagging, Transition, TransitionStorageClass,
|
||||
VersioningConfiguration, WebsiteConfiguration,
|
||||
};
|
||||
use s3s::xml;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Set only by `scripts/gen_bucket_config_goldens.sh`. Every pair test then
|
||||
/// writes its `.out.xml` (and, for the `rustfs` shape, its `.in.xml`) and
|
||||
/// fails on purpose, so a write run can never be mistaken for a verification.
|
||||
const WRITE_ENV: &str = "BUCKET_CONFIG_GOLDENS_WRITE";
|
||||
|
||||
fn goldens_dir() -> PathBuf {
|
||||
Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("tests")
|
||||
.join("fixtures")
|
||||
.join("bucket-config-goldens")
|
||||
}
|
||||
|
||||
fn pair_path(family: &str, shape: &str, side: &str) -> PathBuf {
|
||||
goldens_dir().join(family).join(format!("{shape}.{side}.xml"))
|
||||
}
|
||||
|
||||
fn read(path: &Path) -> Vec<u8> {
|
||||
std::fs::read(path).unwrap_or_else(|err| panic!("read {}: {err}", path.display()))
|
||||
}
|
||||
|
||||
fn write(path: &Path, bytes: &[u8]) {
|
||||
std::fs::write(path, bytes).unwrap_or_else(|err| panic!("write {}: {err}", path.display()));
|
||||
}
|
||||
|
||||
fn contains(haystack: &[u8], needle: &[u8]) -> bool {
|
||||
haystack.windows(needle.len()).any(|window| window == needle)
|
||||
}
|
||||
|
||||
fn round_trip<T>(input: &[u8], label: &str) -> Vec<u8>
|
||||
where
|
||||
T: for<'xml> xml::Deserialize<'xml> + xml::Serialize,
|
||||
{
|
||||
let parsed: T = deserialize(input).unwrap_or_else(|err| panic!("{label}: deserialize failed: {err}"));
|
||||
serialize(&parsed).unwrap_or_else(|err| panic!("{label}: serialize failed: {err}"))
|
||||
}
|
||||
|
||||
/// Byte comparison with a readable report. Parsed values are never compared.
|
||||
fn assert_bytes_eq(actual: &[u8], expected: &[u8], context: &str) {
|
||||
if actual != expected {
|
||||
panic!(
|
||||
"{context}\n--- expected ({} bytes) ---\n{}\n--- actual ({} bytes) ---\n{}\n",
|
||||
expected.len(),
|
||||
String::from_utf8_lossy(expected),
|
||||
actual.len(),
|
||||
String::from_utf8_lossy(actual),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks one `<family>/<shape>` pair. `typed` is the value RustFS writes for
|
||||
/// the `rustfs` shape; the `aws` and `minio` shapes pass `None`.
|
||||
fn check_pair<T>(family: &str, shape: &str, typed: Option<&T>)
|
||||
where
|
||||
T: for<'xml> xml::Deserialize<'xml> + xml::Serialize,
|
||||
{
|
||||
let in_path = pair_path(family, shape, "in");
|
||||
let out_path = pair_path(family, shape, "out");
|
||||
let label = format!("{family}/{shape}");
|
||||
|
||||
if std::env::var_os(WRITE_ENV).is_some() {
|
||||
if let Some(value) = typed {
|
||||
let written = serialize(value).unwrap_or_else(|err| panic!("{label}: typed value failed to serialize: {err}"));
|
||||
write(&in_path, &written);
|
||||
}
|
||||
let input = read(&in_path);
|
||||
write(&out_path, &round_trip::<T>(&input, &label));
|
||||
panic!(
|
||||
"{WRITE_ENV} is set: wrote {} without verifying anything; rerun without {WRITE_ENV}",
|
||||
out_path.display()
|
||||
);
|
||||
}
|
||||
|
||||
let input = read(&in_path);
|
||||
let expected = read(&out_path);
|
||||
if let Some(value) = typed {
|
||||
let written = serialize(value).unwrap_or_else(|err| panic!("{label}: typed value failed to serialize: {err}"));
|
||||
assert_bytes_eq(
|
||||
&written,
|
||||
&input,
|
||||
&format!(
|
||||
"{label}: RustFS no longer writes the pinned bytes for the typed value ({})",
|
||||
in_path.display()
|
||||
),
|
||||
);
|
||||
}
|
||||
let actual = round_trip::<T>(&input, &label);
|
||||
assert_bytes_eq(
|
||||
&actual,
|
||||
&expected,
|
||||
&format!("{label}: deserialize->serialize bytes drifted from {}", out_path.display()),
|
||||
);
|
||||
let again = round_trip::<T>(&expected, &label);
|
||||
assert_bytes_eq(
|
||||
&again,
|
||||
&expected,
|
||||
&format!("{label}: the golden is not a fixed point of deserialize->serialize"),
|
||||
);
|
||||
}
|
||||
|
||||
/// The typed values behind every `rustfs.in.xml`: what RustFS writes today
|
||||
/// through `serialize` when it persists a configuration it built itself.
|
||||
/// MinIO-only members (`DelMarkerExpiration`, `ExcludeFolders`,
|
||||
/// `ExcludedPrefixes`, `DeleteReplication`) are included where RustFS accepts
|
||||
/// them, so the `rustfs` shape is the one that pins their encoding.
|
||||
mod rustfs_values {
|
||||
use super::*;
|
||||
|
||||
fn tag(key: &str, value: &str) -> Tag {
|
||||
Tag {
|
||||
key: Some(key.to_owned()),
|
||||
value: Some(value.to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn notification() -> NotificationConfiguration {
|
||||
NotificationConfiguration {
|
||||
queue_configurations: Some(vec![QueueConfiguration {
|
||||
events: vec![
|
||||
Event::from("s3:ObjectCreated:*".to_owned()),
|
||||
Event::from("s3:ObjectRemoved:*".to_owned()),
|
||||
],
|
||||
filter: Some(NotificationConfigurationFilter {
|
||||
key: Some(S3KeyFilter {
|
||||
filter_rules: Some(vec![FilterRule {
|
||||
name: Some(FilterRuleName::from_static(FilterRuleName::PREFIX)),
|
||||
value: Some("uploads/".to_owned()),
|
||||
}]),
|
||||
}),
|
||||
}),
|
||||
id: Some("rustfs-webhook".to_owned()),
|
||||
queue_arn: "arn:rustfs:sqs::primary:webhook".to_owned(),
|
||||
}]),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn lifecycle() -> BucketLifecycleConfiguration {
|
||||
BucketLifecycleConfiguration {
|
||||
rules: vec![
|
||||
LifecycleRule {
|
||||
abort_incomplete_multipart_upload: Some(AbortIncompleteMultipartUpload {
|
||||
days_after_initiation: Some(3),
|
||||
}),
|
||||
del_marker_expiration: Some(DelMarkerExpiration { days: Some(7) }),
|
||||
expiration: Some(LifecycleExpiration {
|
||||
days: Some(30),
|
||||
..Default::default()
|
||||
}),
|
||||
filter: Some(LifecycleRuleFilter {
|
||||
prefix: Some("tmp/".to_owned()),
|
||||
..Default::default()
|
||||
}),
|
||||
id: Some("expire-tmp".to_owned()),
|
||||
noncurrent_version_expiration: Some(NoncurrentVersionExpiration {
|
||||
newer_noncurrent_versions: Some(2),
|
||||
noncurrent_days: Some(7),
|
||||
}),
|
||||
noncurrent_version_transitions: None,
|
||||
prefix: None,
|
||||
status: ExpirationStatus::from_static(ExpirationStatus::ENABLED),
|
||||
transitions: None,
|
||||
},
|
||||
LifecycleRule {
|
||||
abort_incomplete_multipart_upload: None,
|
||||
del_marker_expiration: None,
|
||||
expiration: None,
|
||||
filter: Some(LifecycleRuleFilter {
|
||||
and: Some(LifecycleRuleAndOperator {
|
||||
prefix: Some("archive/".to_owned()),
|
||||
tags: Some(vec![tag("tier", "cold")]),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
}),
|
||||
id: Some("tier-cold".to_owned()),
|
||||
noncurrent_version_expiration: None,
|
||||
noncurrent_version_transitions: None,
|
||||
prefix: None,
|
||||
status: ExpirationStatus::from_static(ExpirationStatus::ENABLED),
|
||||
transitions: Some(vec![Transition {
|
||||
days: Some(90),
|
||||
storage_class: Some(TransitionStorageClass::from_static("COLDTIER")),
|
||||
..Default::default()
|
||||
}]),
|
||||
},
|
||||
],
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn object_lock() -> ObjectLockConfiguration {
|
||||
ObjectLockConfiguration {
|
||||
object_lock_enabled: Some(ObjectLockEnabled::from_static(ObjectLockEnabled::ENABLED)),
|
||||
rule: Some(ObjectLockRule {
|
||||
default_retention: Some(DefaultRetention {
|
||||
days: Some(30),
|
||||
mode: Some(ObjectLockRetentionMode::from_static(ObjectLockRetentionMode::GOVERNANCE)),
|
||||
years: None,
|
||||
}),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn versioning() -> VersioningConfiguration {
|
||||
VersioningConfiguration {
|
||||
exclude_folders: Some(true),
|
||||
excluded_prefixes: Some(vec![
|
||||
ExcludedPrefix {
|
||||
prefix: Some("tmp/".to_owned()),
|
||||
},
|
||||
ExcludedPrefix {
|
||||
prefix: Some("cache/".to_owned()),
|
||||
},
|
||||
]),
|
||||
mfa_delete: None,
|
||||
status: Some(BucketVersioningStatus::from_static(BucketVersioningStatus::ENABLED)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn encryption() -> ServerSideEncryptionConfiguration {
|
||||
ServerSideEncryptionConfiguration {
|
||||
rules: vec![ServerSideEncryptionRule {
|
||||
apply_server_side_encryption_by_default: Some(ServerSideEncryptionByDefault {
|
||||
kms_master_key_id: Some("rustfs-bucket-key".to_owned()),
|
||||
sse_algorithm: ServerSideEncryption::from_static(ServerSideEncryption::AWS_KMS),
|
||||
}),
|
||||
blocked_encryption_types: None,
|
||||
bucket_key_enabled: Some(false),
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn tagging() -> Tagging {
|
||||
Tagging {
|
||||
tag_set: vec![tag("team", "storage"), tag("env", "staging")],
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn replication() -> ReplicationConfiguration {
|
||||
ReplicationConfiguration {
|
||||
role: String::new(),
|
||||
rules: vec![ReplicationRule {
|
||||
delete_marker_replication: Some(DeleteMarkerReplication {
|
||||
status: Some(DeleteMarkerReplicationStatus::from_static(DeleteMarkerReplicationStatus::ENABLED)),
|
||||
}),
|
||||
delete_replication: Some(DeleteReplication {
|
||||
status: DeleteReplicationStatus::from_static(DeleteReplicationStatus::ENABLED),
|
||||
}),
|
||||
destination: Destination {
|
||||
bucket: "arn:rustfs:replication::0b6f3c2a-5e6a-4d2b-9c1e-7a8f9d0e1b2c:interop-dr".to_owned(),
|
||||
storage_class: Some(StorageClass::from_static(StorageClass::STANDARD)),
|
||||
..Default::default()
|
||||
},
|
||||
existing_object_replication: Some(ExistingObjectReplication {
|
||||
status: ExistingObjectReplicationStatus::from_static(ExistingObjectReplicationStatus::ENABLED),
|
||||
}),
|
||||
filter: Some(ReplicationRuleFilter {
|
||||
and: Some(ReplicationRuleAndOperator {
|
||||
prefix: Some("docs/".to_owned()),
|
||||
tags: Some(vec![tag("replicate", "yes")]),
|
||||
}),
|
||||
..Default::default()
|
||||
}),
|
||||
id: Some("rustfs-dr".to_owned()),
|
||||
prefix: None,
|
||||
priority: Some(1),
|
||||
source_selection_criteria: Some(SourceSelectionCriteria {
|
||||
replica_modifications: Some(ReplicaModifications {
|
||||
status: ReplicaModificationsStatus::from_static(ReplicaModificationsStatus::ENABLED),
|
||||
}),
|
||||
sse_kms_encrypted_objects: None,
|
||||
}),
|
||||
status: ReplicationRuleStatus::from_static(ReplicationRuleStatus::ENABLED),
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn cors() -> CORSConfiguration {
|
||||
CORSConfiguration {
|
||||
cors_rules: vec![CORSRule {
|
||||
allowed_headers: Some(vec!["Authorization".to_owned(), "Content-Type".to_owned()]),
|
||||
allowed_methods: vec!["GET".to_owned(), "PUT".to_owned()],
|
||||
allowed_origins: vec!["https://app.example.internal".to_owned()],
|
||||
expose_headers: Some(vec!["ETag".to_owned(), "x-amz-request-id".to_owned()]),
|
||||
id: Some("rustfs-app".to_owned()),
|
||||
max_age_seconds: Some(3600),
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn logging() -> BucketLoggingStatus {
|
||||
BucketLoggingStatus {
|
||||
logging_enabled: Some(LoggingEnabled {
|
||||
target_bucket: "rustfs-access-logs".to_owned(),
|
||||
target_grants: None,
|
||||
target_object_key_format: None,
|
||||
target_prefix: "buckets/interop/".to_owned(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn website() -> WebsiteConfiguration {
|
||||
WebsiteConfiguration {
|
||||
error_document: Some(ErrorDocument {
|
||||
key: "404.html".to_owned(),
|
||||
}),
|
||||
index_document: Some(IndexDocument {
|
||||
suffix: "index.html".to_owned(),
|
||||
}),
|
||||
redirect_all_requests_to: None,
|
||||
routing_rules: Some(vec![RoutingRule {
|
||||
condition: Some(Condition {
|
||||
http_error_code_returned_equals: None,
|
||||
key_prefix_equals: Some("legacy/".to_owned()),
|
||||
}),
|
||||
redirect: Redirect {
|
||||
host_name: None,
|
||||
http_redirect_code: Some("301".to_owned()),
|
||||
protocol: None,
|
||||
replace_key_prefix_with: Some("current/".to_owned()),
|
||||
replace_key_with: None,
|
||||
},
|
||||
}]),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn accelerate() -> AccelerateConfiguration {
|
||||
AccelerateConfiguration {
|
||||
status: Some(BucketAccelerateStatus::from_static(BucketAccelerateStatus::SUSPENDED)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn request_payment() -> RequestPaymentConfiguration {
|
||||
RequestPaymentConfiguration {
|
||||
payer: Payer::from_static(Payer::BUCKET_OWNER),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn public_access_block() -> PublicAccessBlockConfiguration {
|
||||
PublicAccessBlockConfiguration {
|
||||
block_public_acls: Some(true),
|
||||
block_public_policy: Some(false),
|
||||
ignore_public_acls: Some(true),
|
||||
restrict_public_buckets: Some(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// One test per `<family>/<shape>` pair: 13 families x 3 shapes = 39.
|
||||
mod bucket_config_goldens {
|
||||
use super::*;
|
||||
|
||||
macro_rules! golden {
|
||||
($name:ident, $family:literal, $shape:literal, $ty:ty) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
check_pair::<$ty>($family, $shape, None);
|
||||
}
|
||||
};
|
||||
($name:ident, $family:literal, rustfs = $value:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
check_pair($family, "rustfs", Some(&$value));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
golden!(notification_aws, "notification", "aws", NotificationConfiguration);
|
||||
golden!(notification_minio, "notification", "minio", NotificationConfiguration);
|
||||
golden!(notification_rustfs, "notification", rustfs = rustfs_values::notification());
|
||||
|
||||
golden!(lifecycle_aws, "lifecycle", "aws", BucketLifecycleConfiguration);
|
||||
golden!(lifecycle_minio, "lifecycle", "minio", BucketLifecycleConfiguration);
|
||||
golden!(lifecycle_rustfs, "lifecycle", rustfs = rustfs_values::lifecycle());
|
||||
|
||||
golden!(object_lock_aws, "object_lock", "aws", ObjectLockConfiguration);
|
||||
golden!(object_lock_minio, "object_lock", "minio", ObjectLockConfiguration);
|
||||
golden!(object_lock_rustfs, "object_lock", rustfs = rustfs_values::object_lock());
|
||||
|
||||
golden!(versioning_aws, "versioning", "aws", VersioningConfiguration);
|
||||
golden!(versioning_minio, "versioning", "minio", VersioningConfiguration);
|
||||
golden!(versioning_rustfs, "versioning", rustfs = rustfs_values::versioning());
|
||||
|
||||
golden!(encryption_aws, "encryption", "aws", ServerSideEncryptionConfiguration);
|
||||
golden!(encryption_minio, "encryption", "minio", ServerSideEncryptionConfiguration);
|
||||
golden!(encryption_rustfs, "encryption", rustfs = rustfs_values::encryption());
|
||||
|
||||
golden!(tagging_aws, "tagging", "aws", Tagging);
|
||||
golden!(tagging_minio, "tagging", "minio", Tagging);
|
||||
golden!(tagging_rustfs, "tagging", rustfs = rustfs_values::tagging());
|
||||
|
||||
/// `replication/aws.in.xml` is the unknown-element sample from
|
||||
/// `crates/replication/src/config.rs`
|
||||
/// (`s3_xml_parser_discards_unknown_replication_elements_before_validation`).
|
||||
/// Beyond the byte golden, the pair proves the root-level `FutureTopLevel`
|
||||
/// element is skipped on read and never re-emitted on write.
|
||||
#[test]
|
||||
fn replication_aws() {
|
||||
check_pair::<ReplicationConfiguration>("replication", "aws", None);
|
||||
let input = read(&pair_path("replication", "aws", "in"));
|
||||
let output = read(&pair_path("replication", "aws", "out"));
|
||||
assert!(
|
||||
contains(&input, b"<FutureTopLevel>future</FutureTopLevel>"),
|
||||
"the sample must still carry the unknown element"
|
||||
);
|
||||
assert!(
|
||||
!contains(&output, b"FutureTopLevel"),
|
||||
"an unknown element must not be re-emitted: {}",
|
||||
String::from_utf8_lossy(&output)
|
||||
);
|
||||
}
|
||||
golden!(replication_minio, "replication", "minio", ReplicationConfiguration);
|
||||
golden!(replication_rustfs, "replication", rustfs = rustfs_values::replication());
|
||||
|
||||
golden!(cors_aws, "cors", "aws", CORSConfiguration);
|
||||
golden!(cors_minio, "cors", "minio", CORSConfiguration);
|
||||
golden!(cors_rustfs, "cors", rustfs = rustfs_values::cors());
|
||||
|
||||
golden!(logging_aws, "logging", "aws", BucketLoggingStatus);
|
||||
golden!(logging_minio, "logging", "minio", BucketLoggingStatus);
|
||||
golden!(logging_rustfs, "logging", rustfs = rustfs_values::logging());
|
||||
|
||||
golden!(website_aws, "website", "aws", WebsiteConfiguration);
|
||||
golden!(website_minio, "website", "minio", WebsiteConfiguration);
|
||||
golden!(website_rustfs, "website", rustfs = rustfs_values::website());
|
||||
|
||||
golden!(accelerate_aws, "accelerate", "aws", AccelerateConfiguration);
|
||||
golden!(accelerate_minio, "accelerate", "minio", AccelerateConfiguration);
|
||||
golden!(accelerate_rustfs, "accelerate", rustfs = rustfs_values::accelerate());
|
||||
|
||||
golden!(request_payment_aws, "request_payment", "aws", RequestPaymentConfiguration);
|
||||
golden!(request_payment_minio, "request_payment", "minio", RequestPaymentConfiguration);
|
||||
golden!(request_payment_rustfs, "request_payment", rustfs = rustfs_values::request_payment());
|
||||
|
||||
golden!(public_access_block_aws, "public_access_block", "aws", PublicAccessBlockConfiguration);
|
||||
golden!(public_access_block_minio, "public_access_block", "minio", PublicAccessBlockConfiguration);
|
||||
golden!(
|
||||
public_access_block_rustfs,
|
||||
"public_access_block",
|
||||
rustfs = rustfs_values::public_access_block()
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
# Persisted bucket-configuration XML goldens (rustfs/backlog#2744)
|
||||
|
||||
Byte-level goldens for the 13 XML families that `BucketMetadata` persists as
|
||||
`*_config_xml`. They pin what `rustfs_ecstore::api::bucket::utils::deserialize`
|
||||
followed by `serialize` emits today, so that the DTO/codec replacement (T1.4)
|
||||
and the persisted-bytes comparison (T3.7) can be proven byte-identical: an
|
||||
older binary must keep reading what a newer one writes, and the reverse.
|
||||
|
||||
Generated at commit: `6b1554003ebf8f2037ffb7da9c9b906527e758da` (2026-10-07T17:40:56Z)
|
||||
|
||||
Checked by `crates/ecstore/tests/bucket_config_goldens.rs`
|
||||
(`cargo nextest run -p rustfs-ecstore bucket_config_goldens`, 39 tests).
|
||||
Generated once by `scripts/gen_bucket_config_goldens.sh`, which records the
|
||||
commit above and refuses to run again unless `--force` is passed. Do not
|
||||
regenerate after a DTO or codec replacement: at that point a diff here is the
|
||||
finding, not noise.
|
||||
|
||||
## Layout
|
||||
|
||||
`<family>/<shape>.in.xml` is an input; `<family>/<shape>.out.xml` is the exact
|
||||
bytes of `serialize(deserialize(in))` on the generating commit. The test
|
||||
asserts `bytes == out` (never structural equality), that `out` is a fixed point
|
||||
of the same round trip, and for the `rustfs` shape that `serialize(typed
|
||||
value)` still equals `rustfs.in.xml`.
|
||||
|
||||
| Shape | Source |
|
||||
| --- | --- |
|
||||
| `aws` | Written for this corpus from the public S3 XML schema in the shape of the AWS documentation examples: XML declaration, `xmlns` on the root, indented. No AWS prose is copied. |
|
||||
| `minio` | The bytes a MinIO server persists. Seven families are carved verbatim out of `../minio/bucket_metadata.blob.hex` (MinIO `RELEASE.2025-07-23T15-54-02Z`); the rest are hand-shaped as compact Go `encoding/xml` output with the S3 namespace on the root, because MinIO does not persist those families. |
|
||||
| `rustfs` | The bytes RustFS writes today through `serialize` for a typed value (`rustfs_values` in the test). This shape carries the MinIO-only members RustFS accepts (`DelMarkerExpiration`, `ExcludeFolders`, `ExcludedPrefixes`, `DeleteReplication`). |
|
||||
|
||||
| Family | Root element | `minio.in.xml` provenance |
|
||||
| --- | --- | --- |
|
||||
| `notification` | `NotificationConfiguration` | MinIO fixture |
|
||||
| `lifecycle` | `LifecycleConfiguration` | MinIO fixture (carries the MinIO `ExpiryUpdatedAt` extension) |
|
||||
| `object_lock` | `ObjectLockConfiguration` | MinIO fixture |
|
||||
| `versioning` | `VersioningConfiguration` | MinIO fixture |
|
||||
| `encryption` | `ServerSideEncryptionConfiguration` | MinIO fixture |
|
||||
| `tagging` | `Tagging` | MinIO fixture |
|
||||
| `replication` | `ReplicationConfiguration` | MinIO fixture |
|
||||
| `cors` | `CORSConfiguration` | hand-shaped |
|
||||
| `logging` | `BucketLoggingStatus` | hand-shaped |
|
||||
| `website` | `WebsiteConfiguration` | hand-shaped |
|
||||
| `accelerate` | `AccelerateConfiguration` | hand-shaped |
|
||||
| `request_payment` | `RequestPaymentConfiguration` | hand-shaped |
|
||||
| `public_access_block` | `PublicAccessBlockConfiguration` | hand-shaped |
|
||||
|
||||
## Unknown elements
|
||||
|
||||
`replication/aws.in.xml` is the unknown-element sample from
|
||||
`crates/replication/src/config.rs`
|
||||
(`s3_xml_parser_discards_unknown_replication_elements_before_validation`),
|
||||
re-indented to two spaces. Its golden proves the root-level `FutureTopLevel`
|
||||
element is skipped on read and never re-emitted on write, which is the
|
||||
persisted-configuration exception recorded in the root `AGENTS.md`. On the
|
||||
generating commit every one of the 13 root elements skips unknown children;
|
||||
nested members (`Rule`, `Filter`, `Destination`, ...) reject them. A
|
||||
replacement codec must keep that boundary where it is.
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<AccelerateConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
<Status>Enabled</Status>
|
||||
</AccelerateConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<AccelerateConfiguration><Status>Enabled</Status></AccelerateConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<AccelerateConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Status>Suspended</Status></AccelerateConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<AccelerateConfiguration><Status>Suspended</Status></AccelerateConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<AccelerateConfiguration><Status>Suspended</Status></AccelerateConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<AccelerateConfiguration><Status>Suspended</Status></AccelerateConfiguration>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
<CORSRule>
|
||||
<ID>AllowWebApp</ID>
|
||||
<AllowedOrigin>https://www.example.com</AllowedOrigin>
|
||||
<AllowedMethod>PUT</AllowedMethod>
|
||||
<AllowedMethod>POST</AllowedMethod>
|
||||
<AllowedMethod>DELETE</AllowedMethod>
|
||||
<AllowedHeader>*</AllowedHeader>
|
||||
<ExposeHeader>x-amz-server-side-encryption</ExposeHeader>
|
||||
<MaxAgeSeconds>3000</MaxAgeSeconds>
|
||||
</CORSRule>
|
||||
<CORSRule>
|
||||
<AllowedOrigin>*</AllowedOrigin>
|
||||
<AllowedMethod>GET</AllowedMethod>
|
||||
</CORSRule>
|
||||
</CORSConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<CORSConfiguration><CORSRule><AllowedHeader>*</AllowedHeader><AllowedMethod>PUT</AllowedMethod><AllowedMethod>POST</AllowedMethod><AllowedMethod>DELETE</AllowedMethod><AllowedOrigin>https://www.example.com</AllowedOrigin><ExposeHeader>x-amz-server-side-encryption</ExposeHeader><ID>AllowWebApp</ID><MaxAgeSeconds>3000</MaxAgeSeconds></CORSRule><CORSRule><AllowedMethod>GET</AllowedMethod><AllowedOrigin>*</AllowedOrigin></CORSRule></CORSConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><CORSRule><ID>console</ID><AllowedOrigin>https://console.example.internal</AllowedOrigin><AllowedMethod>GET</AllowedMethod><AllowedMethod>HEAD</AllowedMethod><AllowedHeader>Authorization</AllowedHeader><AllowedHeader>Content-Type</AllowedHeader><ExposeHeader>ETag</ExposeHeader><MaxAgeSeconds>600</MaxAgeSeconds></CORSRule></CORSConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<CORSConfiguration><CORSRule><AllowedHeader>Authorization</AllowedHeader><AllowedHeader>Content-Type</AllowedHeader><AllowedMethod>GET</AllowedMethod><AllowedMethod>HEAD</AllowedMethod><AllowedOrigin>https://console.example.internal</AllowedOrigin><ExposeHeader>ETag</ExposeHeader><ID>console</ID><MaxAgeSeconds>600</MaxAgeSeconds></CORSRule></CORSConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<CORSConfiguration><CORSRule><AllowedHeader>Authorization</AllowedHeader><AllowedHeader>Content-Type</AllowedHeader><AllowedMethod>GET</AllowedMethod><AllowedMethod>PUT</AllowedMethod><AllowedOrigin>https://app.example.internal</AllowedOrigin><ExposeHeader>ETag</ExposeHeader><ExposeHeader>x-amz-request-id</ExposeHeader><ID>rustfs-app</ID><MaxAgeSeconds>3600</MaxAgeSeconds></CORSRule></CORSConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<CORSConfiguration><CORSRule><AllowedHeader>Authorization</AllowedHeader><AllowedHeader>Content-Type</AllowedHeader><AllowedMethod>GET</AllowedMethod><AllowedMethod>PUT</AllowedMethod><AllowedOrigin>https://app.example.internal</AllowedOrigin><ExposeHeader>ETag</ExposeHeader><ExposeHeader>x-amz-request-id</ExposeHeader><ID>rustfs-app</ID><MaxAgeSeconds>3600</MaxAgeSeconds></CORSRule></CORSConfiguration>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ServerSideEncryptionConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
<Rule>
|
||||
<ApplyServerSideEncryptionByDefault>
|
||||
<SSEAlgorithm>aws:kms</SSEAlgorithm>
|
||||
<KMSMasterKeyID>arn:aws:kms:us-east-1:123456789012:key/1234abcd-12ab-34cd-56ef-1234567890ab</KMSMasterKeyID>
|
||||
</ApplyServerSideEncryptionByDefault>
|
||||
<BucketKeyEnabled>true</BucketKeyEnabled>
|
||||
</Rule>
|
||||
</ServerSideEncryptionConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<ServerSideEncryptionConfiguration><Rule><ApplyServerSideEncryptionByDefault><KMSMasterKeyID>arn:aws:kms:us-east-1:123456789012:key/1234abcd-12ab-34cd-56ef-1234567890ab</KMSMasterKeyID><SSEAlgorithm>aws:kms</SSEAlgorithm></ApplyServerSideEncryptionByDefault><BucketKeyEnabled>true</BucketKeyEnabled></Rule></ServerSideEncryptionConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<ServerSideEncryptionConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Rule><ApplyServerSideEncryptionByDefault><SSEAlgorithm>AES256</SSEAlgorithm></ApplyServerSideEncryptionByDefault></Rule></ServerSideEncryptionConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<ServerSideEncryptionConfiguration><Rule><ApplyServerSideEncryptionByDefault><SSEAlgorithm>AES256</SSEAlgorithm></ApplyServerSideEncryptionByDefault></Rule></ServerSideEncryptionConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<ServerSideEncryptionConfiguration><Rule><ApplyServerSideEncryptionByDefault><KMSMasterKeyID>rustfs-bucket-key</KMSMasterKeyID><SSEAlgorithm>aws:kms</SSEAlgorithm></ApplyServerSideEncryptionByDefault><BucketKeyEnabled>false</BucketKeyEnabled></Rule></ServerSideEncryptionConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<ServerSideEncryptionConfiguration><Rule><ApplyServerSideEncryptionByDefault><KMSMasterKeyID>rustfs-bucket-key</KMSMasterKeyID><SSEAlgorithm>aws:kms</SSEAlgorithm></ApplyServerSideEncryptionByDefault><BucketKeyEnabled>false</BucketKeyEnabled></Rule></ServerSideEncryptionConfiguration>
|
||||
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<LifecycleConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
<Rule>
|
||||
<ID>Archive and then delete rule</ID>
|
||||
<Filter>
|
||||
<And>
|
||||
<Prefix>projectdocs/</Prefix>
|
||||
<Tag>
|
||||
<Key>department</Key>
|
||||
<Value>finance</Value>
|
||||
</Tag>
|
||||
<ObjectSizeGreaterThan>1024</ObjectSizeGreaterThan>
|
||||
</And>
|
||||
</Filter>
|
||||
<Status>Enabled</Status>
|
||||
<Transition>
|
||||
<Days>30</Days>
|
||||
<StorageClass>STANDARD_IA</StorageClass>
|
||||
</Transition>
|
||||
<Transition>
|
||||
<Days>365</Days>
|
||||
<StorageClass>GLACIER</StorageClass>
|
||||
</Transition>
|
||||
<Expiration>
|
||||
<Days>3650</Days>
|
||||
</Expiration>
|
||||
<NoncurrentVersionTransition>
|
||||
<NoncurrentDays>90</NoncurrentDays>
|
||||
<StorageClass>GLACIER</StorageClass>
|
||||
</NoncurrentVersionTransition>
|
||||
<NoncurrentVersionExpiration>
|
||||
<NoncurrentDays>400</NoncurrentDays>
|
||||
<NewerNoncurrentVersions>3</NewerNoncurrentVersions>
|
||||
</NoncurrentVersionExpiration>
|
||||
<AbortIncompleteMultipartUpload>
|
||||
<DaysAfterInitiation>7</DaysAfterInitiation>
|
||||
</AbortIncompleteMultipartUpload>
|
||||
</Rule>
|
||||
<Rule>
|
||||
<ID>Expire delete markers</ID>
|
||||
<Filter>
|
||||
<Prefix>logs/</Prefix>
|
||||
</Filter>
|
||||
<Status>Disabled</Status>
|
||||
<Expiration>
|
||||
<ExpiredObjectDeleteMarker>true</ExpiredObjectDeleteMarker>
|
||||
</Expiration>
|
||||
</Rule>
|
||||
</LifecycleConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<LifecycleConfiguration><Rule><AbortIncompleteMultipartUpload><DaysAfterInitiation>7</DaysAfterInitiation></AbortIncompleteMultipartUpload><Expiration><Days>3650</Days></Expiration><Filter><And><ObjectSizeGreaterThan>1024</ObjectSizeGreaterThan><Prefix>projectdocs/</Prefix><Tag><Key>department</Key><Value>finance</Value></Tag></And></Filter><ID>Archive and then delete rule</ID><NoncurrentVersionExpiration><NewerNoncurrentVersions>3</NewerNoncurrentVersions><NoncurrentDays>400</NoncurrentDays></NoncurrentVersionExpiration><NoncurrentVersionTransition><NoncurrentDays>90</NoncurrentDays><StorageClass>GLACIER</StorageClass></NoncurrentVersionTransition><Status>Enabled</Status><Transition><Days>30</Days><StorageClass>STANDARD_IA</StorageClass></Transition><Transition><Days>365</Days><StorageClass>GLACIER</StorageClass></Transition></Rule><Rule><Expiration><ExpiredObjectDeleteMarker>true</ExpiredObjectDeleteMarker></Expiration><Filter><Prefix>logs/</Prefix></Filter><ID>Expire delete markers</ID><Status>Disabled</Status></Rule></LifecycleConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<LifecycleConfiguration><Rule><ID>d96i4g89k8h26a95st60</ID><Status>Enabled</Status><Filter><Prefix></Prefix></Filter><Expiration><Days>30</Days></Expiration><NoncurrentVersionExpiration><NoncurrentDays>7</NoncurrentDays></NoncurrentVersionExpiration></Rule><ExpiryUpdatedAt>2026-07-07T15:58:57.337315Z</ExpiryUpdatedAt></LifecycleConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<LifecycleConfiguration><ExpiryUpdatedAt>2026-07-07T15:58:57.337Z</ExpiryUpdatedAt><Rule><Expiration><Days>30</Days></Expiration><Filter><Prefix></Prefix></Filter><ID>d96i4g89k8h26a95st60</ID><NoncurrentVersionExpiration><NoncurrentDays>7</NoncurrentDays></NoncurrentVersionExpiration><Status>Enabled</Status></Rule></LifecycleConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<LifecycleConfiguration><Rule><AbortIncompleteMultipartUpload><DaysAfterInitiation>3</DaysAfterInitiation></AbortIncompleteMultipartUpload><DelMarkerExpiration><Days>7</Days></DelMarkerExpiration><Expiration><Days>30</Days></Expiration><Filter><Prefix>tmp/</Prefix></Filter><ID>expire-tmp</ID><NoncurrentVersionExpiration><NewerNoncurrentVersions>2</NewerNoncurrentVersions><NoncurrentDays>7</NoncurrentDays></NoncurrentVersionExpiration><Status>Enabled</Status></Rule><Rule><Filter><And><Prefix>archive/</Prefix><Tag><Key>tier</Key><Value>cold</Value></Tag></And></Filter><ID>tier-cold</ID><Status>Enabled</Status><Transition><Days>90</Days><StorageClass>COLDTIER</StorageClass></Transition></Rule></LifecycleConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<LifecycleConfiguration><Rule><AbortIncompleteMultipartUpload><DaysAfterInitiation>3</DaysAfterInitiation></AbortIncompleteMultipartUpload><DelMarkerExpiration><Days>7</Days></DelMarkerExpiration><Expiration><Days>30</Days></Expiration><Filter><Prefix>tmp/</Prefix></Filter><ID>expire-tmp</ID><NoncurrentVersionExpiration><NewerNoncurrentVersions>2</NewerNoncurrentVersions><NoncurrentDays>7</NoncurrentDays></NoncurrentVersionExpiration><Status>Enabled</Status></Rule><Rule><Filter><And><Prefix>archive/</Prefix><Tag><Key>tier</Key><Value>cold</Value></Tag></And></Filter><ID>tier-cold</ID><Status>Enabled</Status><Transition><Days>90</Days><StorageClass>COLDTIER</StorageClass></Transition></Rule></LifecycleConfiguration>
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<BucketLoggingStatus xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
<LoggingEnabled>
|
||||
<TargetBucket>amzn-s3-demo-logging-bucket</TargetBucket>
|
||||
<TargetPrefix>logs/</TargetPrefix>
|
||||
<TargetGrants>
|
||||
<Grant>
|
||||
<Grantee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="AmazonCustomerByEmail">
|
||||
<EmailAddress>user@example.com</EmailAddress>
|
||||
</Grantee>
|
||||
<Permission>READ</Permission>
|
||||
</Grant>
|
||||
</TargetGrants>
|
||||
<TargetObjectKeyFormat>
|
||||
<PartitionedPrefix>
|
||||
<PartitionDateSource>EventTime</PartitionDateSource>
|
||||
</PartitionedPrefix>
|
||||
</TargetObjectKeyFormat>
|
||||
</LoggingEnabled>
|
||||
</BucketLoggingStatus>
|
||||
@@ -0,0 +1 @@
|
||||
<BucketLoggingStatus><LoggingEnabled><TargetBucket>amzn-s3-demo-logging-bucket</TargetBucket><TargetGrants><Grant><Grantee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="AmazonCustomerByEmail"><EmailAddress>user@example.com</EmailAddress></Grantee><Permission>READ</Permission></Grant></TargetGrants><TargetObjectKeyFormat><PartitionedPrefix><PartitionDateSource>EventTime</PartitionDateSource></PartitionedPrefix></TargetObjectKeyFormat><TargetPrefix>logs/</TargetPrefix></LoggingEnabled></BucketLoggingStatus>
|
||||
@@ -0,0 +1 @@
|
||||
<BucketLoggingStatus xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><LoggingEnabled><TargetBucket>interop-logs</TargetBucket><TargetPrefix>interop/</TargetPrefix></LoggingEnabled></BucketLoggingStatus>
|
||||
@@ -0,0 +1 @@
|
||||
<BucketLoggingStatus><LoggingEnabled><TargetBucket>interop-logs</TargetBucket><TargetPrefix>interop/</TargetPrefix></LoggingEnabled></BucketLoggingStatus>
|
||||
@@ -0,0 +1 @@
|
||||
<BucketLoggingStatus><LoggingEnabled><TargetBucket>rustfs-access-logs</TargetBucket><TargetPrefix>buckets/interop/</TargetPrefix></LoggingEnabled></BucketLoggingStatus>
|
||||
@@ -0,0 +1 @@
|
||||
<BucketLoggingStatus><LoggingEnabled><TargetBucket>rustfs-access-logs</TargetBucket><TargetPrefix>buckets/interop/</TargetPrefix></LoggingEnabled></BucketLoggingStatus>
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<NotificationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
<TopicConfiguration>
|
||||
<Id>ImageUploads</Id>
|
||||
<Topic>arn:aws:sns:us-east-1:123456789012:s3-events</Topic>
|
||||
<Event>s3:ObjectCreated:*</Event>
|
||||
<Filter>
|
||||
<S3Key>
|
||||
<FilterRule>
|
||||
<Name>prefix</Name>
|
||||
<Value>images/</Value>
|
||||
</FilterRule>
|
||||
<FilterRule>
|
||||
<Name>suffix</Name>
|
||||
<Value>.jpg</Value>
|
||||
</FilterRule>
|
||||
</S3Key>
|
||||
</Filter>
|
||||
</TopicConfiguration>
|
||||
<QueueConfiguration>
|
||||
<Id>Deletions</Id>
|
||||
<Queue>arn:aws:sqs:us-east-1:123456789012:s3-deletes</Queue>
|
||||
<Event>s3:ObjectRemoved:*</Event>
|
||||
</QueueConfiguration>
|
||||
<CloudFunctionConfiguration>
|
||||
<Id>Thumbnails</Id>
|
||||
<CloudFunction>arn:aws:lambda:us-east-1:123456789012:function:thumbnail</CloudFunction>
|
||||
<Event>s3:ObjectCreated:Put</Event>
|
||||
</CloudFunctionConfiguration>
|
||||
</NotificationConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<NotificationConfiguration><CloudFunctionConfiguration><Event>s3:ObjectCreated:Put</Event><Id>Thumbnails</Id><CloudFunction>arn:aws:lambda:us-east-1:123456789012:function:thumbnail</CloudFunction></CloudFunctionConfiguration><QueueConfiguration><Event>s3:ObjectRemoved:*</Event><Id>Deletions</Id><Queue>arn:aws:sqs:us-east-1:123456789012:s3-deletes</Queue></QueueConfiguration><TopicConfiguration><Event>s3:ObjectCreated:*</Event><Filter><S3Key><FilterRule><Name>prefix</Name><Value>images/</Value></FilterRule><FilterRule><Name>suffix</Name><Value>.jpg</Value></FilterRule></S3Key></Filter><Id>ImageUploads</Id><Topic>arn:aws:sns:us-east-1:123456789012:s3-events</Topic></TopicConfiguration></NotificationConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<NotificationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><QueueConfiguration><Id></Id><Event>s3:ObjectCreated:*</Event><Queue>arn:minio:sqs::primary:webhook</Queue></QueueConfiguration></NotificationConfiguration>
|
||||
+1
@@ -0,0 +1 @@
|
||||
<NotificationConfiguration><QueueConfiguration><Event>s3:ObjectCreated:*</Event><Id></Id><Queue>arn:minio:sqs::primary:webhook</Queue></QueueConfiguration></NotificationConfiguration>
|
||||
+1
@@ -0,0 +1 @@
|
||||
<NotificationConfiguration><QueueConfiguration><Event>s3:ObjectCreated:*</Event><Event>s3:ObjectRemoved:*</Event><Filter><S3Key><FilterRule><Name>prefix</Name><Value>uploads/</Value></FilterRule></S3Key></Filter><Id>rustfs-webhook</Id><Queue>arn:rustfs:sqs::primary:webhook</Queue></QueueConfiguration></NotificationConfiguration>
|
||||
+1
@@ -0,0 +1 @@
|
||||
<NotificationConfiguration><QueueConfiguration><Event>s3:ObjectCreated:*</Event><Event>s3:ObjectRemoved:*</Event><Filter><S3Key><FilterRule><Name>prefix</Name><Value>uploads/</Value></FilterRule></S3Key></Filter><Id>rustfs-webhook</Id><Queue>arn:rustfs:sqs::primary:webhook</Queue></QueueConfiguration></NotificationConfiguration>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ObjectLockConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
<ObjectLockEnabled>Enabled</ObjectLockEnabled>
|
||||
<Rule>
|
||||
<DefaultRetention>
|
||||
<Mode>COMPLIANCE</Mode>
|
||||
<Years>1</Years>
|
||||
</DefaultRetention>
|
||||
</Rule>
|
||||
</ObjectLockConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<ObjectLockConfiguration><ObjectLockEnabled>Enabled</ObjectLockEnabled><Rule><DefaultRetention><Mode>COMPLIANCE</Mode><Years>1</Years></DefaultRetention></Rule></ObjectLockConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<ObjectLockConfiguration><ObjectLockEnabled>Enabled</ObjectLockEnabled><Rule><DefaultRetention><Mode>GOVERNANCE</Mode><Days>7</Days></DefaultRetention></Rule></ObjectLockConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<ObjectLockConfiguration><ObjectLockEnabled>Enabled</ObjectLockEnabled><Rule><DefaultRetention><Days>7</Days><Mode>GOVERNANCE</Mode></DefaultRetention></Rule></ObjectLockConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<ObjectLockConfiguration><ObjectLockEnabled>Enabled</ObjectLockEnabled><Rule><DefaultRetention><Days>30</Days><Mode>GOVERNANCE</Mode></DefaultRetention></Rule></ObjectLockConfiguration>
|
||||
+1
@@ -0,0 +1 @@
|
||||
<ObjectLockConfiguration><ObjectLockEnabled>Enabled</ObjectLockEnabled><Rule><DefaultRetention><Days>30</Days><Mode>GOVERNANCE</Mode></DefaultRetention></Rule></ObjectLockConfiguration>
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<PublicAccessBlockConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
<BlockPublicAcls>TRUE</BlockPublicAcls>
|
||||
<IgnorePublicAcls>TRUE</IgnorePublicAcls>
|
||||
<BlockPublicPolicy>TRUE</BlockPublicPolicy>
|
||||
<RestrictPublicBuckets>TRUE</RestrictPublicBuckets>
|
||||
</PublicAccessBlockConfiguration>
|
||||
+1
@@ -0,0 +1 @@
|
||||
<PublicAccessBlockConfiguration><BlockPublicAcls>true</BlockPublicAcls><BlockPublicPolicy>true</BlockPublicPolicy><IgnorePublicAcls>true</IgnorePublicAcls><RestrictPublicBuckets>true</RestrictPublicBuckets></PublicAccessBlockConfiguration>
|
||||
+1
@@ -0,0 +1 @@
|
||||
<PublicAccessBlockConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><BlockPublicAcls>true</BlockPublicAcls><IgnorePublicAcls>false</IgnorePublicAcls><BlockPublicPolicy>true</BlockPublicPolicy><RestrictPublicBuckets>false</RestrictPublicBuckets></PublicAccessBlockConfiguration>
|
||||
+1
@@ -0,0 +1 @@
|
||||
<PublicAccessBlockConfiguration><BlockPublicAcls>true</BlockPublicAcls><BlockPublicPolicy>true</BlockPublicPolicy><IgnorePublicAcls>false</IgnorePublicAcls><RestrictPublicBuckets>false</RestrictPublicBuckets></PublicAccessBlockConfiguration>
|
||||
+1
@@ -0,0 +1 @@
|
||||
<PublicAccessBlockConfiguration><BlockPublicAcls>true</BlockPublicAcls><BlockPublicPolicy>false</BlockPublicPolicy><IgnorePublicAcls>true</IgnorePublicAcls><RestrictPublicBuckets>false</RestrictPublicBuckets></PublicAccessBlockConfiguration>
|
||||
+1
@@ -0,0 +1 @@
|
||||
<PublicAccessBlockConfiguration><BlockPublicAcls>true</BlockPublicAcls><BlockPublicPolicy>false</BlockPublicPolicy><IgnorePublicAcls>true</IgnorePublicAcls><RestrictPublicBuckets>false</RestrictPublicBuckets></PublicAccessBlockConfiguration>
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
<ReplicationConfiguration>
|
||||
<Role></Role>
|
||||
<FutureTopLevel>future</FutureTopLevel>
|
||||
<Rule>
|
||||
<ID>unknown</ID>
|
||||
<Status>Enabled</Status>
|
||||
<Destination>
|
||||
<Bucket>arn:aws:s3:::destination</Bucket>
|
||||
</Destination>
|
||||
</Rule>
|
||||
</ReplicationConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<ReplicationConfiguration><Role></Role><Rule><Destination><Bucket>arn:aws:s3:::destination</Bucket></Destination><ID>unknown</ID><Status>Enabled</Status></Rule></ReplicationConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<ReplicationConfiguration><Rule><ID>d96i4m09k8h2vldifkag</ID><Status>Enabled</Status><Priority>1</Priority><DeleteMarkerReplication><Status>Enabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Enabled</Status></DeleteReplication><Destination><Bucket>arn:minio:replication::ef5859af-120a-4218-94b5-be23470f3c60:interop-dr</Bucket></Destination><SourceSelectionCriteria><ReplicaModifications><Status>Enabled</Status></ReplicaModifications></SourceSelectionCriteria><Filter><Prefix></Prefix></Filter><ExistingObjectReplication><Status>Enabled</Status></ExistingObjectReplication></Rule><Role></Role></ReplicationConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<ReplicationConfiguration><Role></Role><Rule><DeleteMarkerReplication><Status>Enabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Enabled</Status></DeleteReplication><Destination><Bucket>arn:minio:replication::ef5859af-120a-4218-94b5-be23470f3c60:interop-dr</Bucket></Destination><ExistingObjectReplication><Status>Enabled</Status></ExistingObjectReplication><Filter><Prefix></Prefix></Filter><ID>d96i4m09k8h2vldifkag</ID><Priority>1</Priority><SourceSelectionCriteria><ReplicaModifications><Status>Enabled</Status></ReplicaModifications></SourceSelectionCriteria><Status>Enabled</Status></Rule></ReplicationConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<ReplicationConfiguration><Role></Role><Rule><DeleteMarkerReplication><Status>Enabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Enabled</Status></DeleteReplication><Destination><Bucket>arn:rustfs:replication::0b6f3c2a-5e6a-4d2b-9c1e-7a8f9d0e1b2c:interop-dr</Bucket><StorageClass>STANDARD</StorageClass></Destination><ExistingObjectReplication><Status>Enabled</Status></ExistingObjectReplication><Filter><And><Prefix>docs/</Prefix><Tag><Key>replicate</Key><Value>yes</Value></Tag></And></Filter><ID>rustfs-dr</ID><Priority>1</Priority><SourceSelectionCriteria><ReplicaModifications><Status>Enabled</Status></ReplicaModifications></SourceSelectionCriteria><Status>Enabled</Status></Rule></ReplicationConfiguration>
|
||||
+1
@@ -0,0 +1 @@
|
||||
<ReplicationConfiguration><Role></Role><Rule><DeleteMarkerReplication><Status>Enabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Enabled</Status></DeleteReplication><Destination><Bucket>arn:rustfs:replication::0b6f3c2a-5e6a-4d2b-9c1e-7a8f9d0e1b2c:interop-dr</Bucket><StorageClass>STANDARD</StorageClass></Destination><ExistingObjectReplication><Status>Enabled</Status></ExistingObjectReplication><Filter><And><Prefix>docs/</Prefix><Tag><Key>replicate</Key><Value>yes</Value></Tag></And></Filter><ID>rustfs-dr</ID><Priority>1</Priority><SourceSelectionCriteria><ReplicaModifications><Status>Enabled</Status></ReplicaModifications></SourceSelectionCriteria><Status>Enabled</Status></Rule></ReplicationConfiguration>
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<RequestPaymentConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
<Payer>Requester</Payer>
|
||||
</RequestPaymentConfiguration>
|
||||
+1
@@ -0,0 +1 @@
|
||||
<RequestPaymentConfiguration><Payer>Requester</Payer></RequestPaymentConfiguration>
|
||||
+1
@@ -0,0 +1 @@
|
||||
<RequestPaymentConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Payer>BucketOwner</Payer></RequestPaymentConfiguration>
|
||||
+1
@@ -0,0 +1 @@
|
||||
<RequestPaymentConfiguration><Payer>BucketOwner</Payer></RequestPaymentConfiguration>
|
||||
+1
@@ -0,0 +1 @@
|
||||
<RequestPaymentConfiguration><Payer>BucketOwner</Payer></RequestPaymentConfiguration>
|
||||
+1
@@ -0,0 +1 @@
|
||||
<RequestPaymentConfiguration><Payer>BucketOwner</Payer></RequestPaymentConfiguration>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Tagging xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
<TagSet>
|
||||
<Tag>
|
||||
<Key>Project</Key>
|
||||
<Value>Project One</Value>
|
||||
</Tag>
|
||||
<Tag>
|
||||
<Key>User</Key>
|
||||
<Value>jsmith</Value>
|
||||
</Tag>
|
||||
</TagSet>
|
||||
</Tagging>
|
||||
@@ -0,0 +1 @@
|
||||
<Tagging><TagSet><Tag><Key>Project</Key><Value>Project One</Value></Tag><Tag><Key>User</Key><Value>jsmith</Value></Tag></TagSet></Tagging>
|
||||
@@ -0,0 +1 @@
|
||||
<Tagging><TagSet><Tag><Key>team</Key><Value>storage</Value></Tag><Tag><Key>env</Key><Value>prod</Value></Tag></TagSet></Tagging>
|
||||
@@ -0,0 +1 @@
|
||||
<Tagging><TagSet><Tag><Key>team</Key><Value>storage</Value></Tag><Tag><Key>env</Key><Value>prod</Value></Tag></TagSet></Tagging>
|
||||
@@ -0,0 +1 @@
|
||||
<Tagging><TagSet><Tag><Key>team</Key><Value>storage</Value></Tag><Tag><Key>env</Key><Value>staging</Value></Tag></TagSet></Tagging>
|
||||
@@ -0,0 +1 @@
|
||||
<Tagging><TagSet><Tag><Key>team</Key><Value>storage</Value></Tag><Tag><Key>env</Key><Value>staging</Value></Tag></TagSet></Tagging>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
<Status>Enabled</Status>
|
||||
<MfaDelete>Disabled</MfaDelete>
|
||||
</VersioningConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<VersioningConfiguration><MfaDelete>Disabled</MfaDelete><Status>Enabled</Status></VersioningConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Status>Enabled</Status></VersioningConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<VersioningConfiguration><Status>Enabled</Status></VersioningConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<VersioningConfiguration><ExcludeFolders>true</ExcludeFolders><ExcludedPrefixes><Prefix>tmp/</Prefix></ExcludedPrefixes><ExcludedPrefixes><Prefix>cache/</Prefix></ExcludedPrefixes><Status>Enabled</Status></VersioningConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<VersioningConfiguration><ExcludeFolders>true</ExcludeFolders><ExcludedPrefixes><Prefix>tmp/</Prefix></ExcludedPrefixes><ExcludedPrefixes><Prefix>cache/</Prefix></ExcludedPrefixes><Status>Enabled</Status></VersioningConfiguration>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<WebsiteConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
<IndexDocument>
|
||||
<Suffix>index.html</Suffix>
|
||||
</IndexDocument>
|
||||
<ErrorDocument>
|
||||
<Key>error.html</Key>
|
||||
</ErrorDocument>
|
||||
<RoutingRules>
|
||||
<RoutingRule>
|
||||
<Condition>
|
||||
<KeyPrefixEquals>docs/</KeyPrefixEquals>
|
||||
<HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
|
||||
</Condition>
|
||||
<Redirect>
|
||||
<Protocol>https</Protocol>
|
||||
<HostName>www.example.com</HostName>
|
||||
<ReplaceKeyPrefixWith>documents/</ReplaceKeyPrefixWith>
|
||||
<HttpRedirectCode>301</HttpRedirectCode>
|
||||
</Redirect>
|
||||
</RoutingRule>
|
||||
</RoutingRules>
|
||||
</WebsiteConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<WebsiteConfiguration><ErrorDocument><Key>error.html</Key></ErrorDocument><IndexDocument><Suffix>index.html</Suffix></IndexDocument><RoutingRules><RoutingRule><Condition><HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals><KeyPrefixEquals>docs/</KeyPrefixEquals></Condition><Redirect><HostName>www.example.com</HostName><HttpRedirectCode>301</HttpRedirectCode><Protocol>https</Protocol><ReplaceKeyPrefixWith>documents/</ReplaceKeyPrefixWith></Redirect></RoutingRule></RoutingRules></WebsiteConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<WebsiteConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><IndexDocument><Suffix>index.html</Suffix></IndexDocument><ErrorDocument><Key>404.html</Key></ErrorDocument></WebsiteConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<WebsiteConfiguration><ErrorDocument><Key>404.html</Key></ErrorDocument><IndexDocument><Suffix>index.html</Suffix></IndexDocument></WebsiteConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<WebsiteConfiguration><ErrorDocument><Key>404.html</Key></ErrorDocument><IndexDocument><Suffix>index.html</Suffix></IndexDocument><RoutingRules><RoutingRule><Condition><KeyPrefixEquals>legacy/</KeyPrefixEquals></Condition><Redirect><HttpRedirectCode>301</HttpRedirectCode><ReplaceKeyPrefixWith>current/</ReplaceKeyPrefixWith></Redirect></RoutingRule></RoutingRules></WebsiteConfiguration>
|
||||
@@ -0,0 +1 @@
|
||||
<WebsiteConfiguration><ErrorDocument><Key>404.html</Key></ErrorDocument><IndexDocument><Suffix>index.html</Suffix></IndexDocument><RoutingRules><RoutingRule><Condition><KeyPrefixEquals>legacy/</KeyPrefixEquals></Condition><Redirect><HttpRedirectCode>301</HttpRedirectCode><ReplaceKeyPrefixWith>current/</ReplaceKeyPrefixWith></Redirect></RoutingRule></RoutingRules></WebsiteConfiguration>
|
||||
@@ -55,6 +55,7 @@ their issue closes.
|
||||
| `e2e-run.sh` | ci-gate | Boots a rustfs server and runs the `s3s-e2e` black-box conformance tool against it | ci.yml `e2e-tests` jobs; `docs/testing/README.md` |
|
||||
| `run_ecstore_validation_suite.sh` | dev-tool | ecstore black-box validation suite (`quick`/`full`/`destructive`/`fuzz` profiles) | `docs/testing/README.md`, `docs/testing/ecstore-validation-suite-design.md` |
|
||||
| `run_e2e_tests.sh` | dev-tool | Local `e2e_test` crate runner (starts a server, applies filters, cleans up) | `crates/e2e_test/README.md` |
|
||||
| `gen_bucket_config_goldens.sh` | dev-tool | One-shot generator for the 13-family persisted bucket-config XML byte goldens; refuses to overwrite without `--force` and records the generating commit | `crates/ecstore/tests/fixtures/bucket-config-goldens/README.md`; `crates/ecstore/tests/bucket_config_goldens.rs` |
|
||||
| `run.sh` | dev-tool | Local rustfs startup wrapper | `make e2e-server`; Justfile |
|
||||
| `run.ps1` | dev-tool | Windows counterpart of `run.sh` | — |
|
||||
| `probe.sh` | dev-tool | Probe-style e2e run | `make probe-e2e` |
|
||||
|
||||
Executable
+117
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env bash
|
||||
# Generates the byte-level goldens for the 13 persisted bucket-configuration
|
||||
# XML families (rustfs/backlog#2744).
|
||||
#
|
||||
# For every `crates/ecstore/tests/fixtures/bucket-config-goldens/<family>/`
|
||||
# the test binary `bucket_config_goldens` writes, in write mode:
|
||||
# - `rustfs.in.xml` the bytes RustFS writes today for the typed value
|
||||
# - `<shape>.out.xml` the exact bytes of deserialize -> serialize for each
|
||||
# `<shape>.in.xml` (shapes: aws, minio, rustfs)
|
||||
#
|
||||
# The goldens are the baseline that the DTO/codec replacement (T1.4) and the
|
||||
# persisted-bytes comparison (T3.7) are judged against, so this script is
|
||||
# meant to run ONCE on the pre-replacement baseline. It refuses to run when
|
||||
# generated files already exist (pass --force to regenerate on purpose) and
|
||||
# when the codec-affecting paths carry uncommitted changes (the commit it
|
||||
# records must describe the codec that produced the bytes).
|
||||
#
|
||||
# Write mode never reports success: every pair test writes its files and then
|
||||
# fails on purpose. The verification that follows is the only green line, and
|
||||
# it must report exactly 39 passed.
|
||||
#
|
||||
# Usage: scripts/gen_bucket_config_goldens.sh [--force]
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
GOLDENS_DIR="crates/ecstore/tests/fixtures/bucket-config-goldens"
|
||||
README="$GOLDENS_DIR/README.md"
|
||||
TEST_BINARY="bucket_config_goldens"
|
||||
WRITE_ENV="BUCKET_CONFIG_GOLDENS_WRITE"
|
||||
EXPECTED_FAMILIES=13
|
||||
EXPECTED_PAIRS=39
|
||||
|
||||
usage() {
|
||||
printf 'Usage: %s [--force]\n' "$0"
|
||||
printf ' --force regenerate even though generated files already exist\n'
|
||||
}
|
||||
|
||||
force=0
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--force) force=1 ;;
|
||||
-h | --help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
printf 'unknown argument: %s\n' "$arg" >&2
|
||||
usage >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ ! -d "$GOLDENS_DIR" || ! -f "$README" ]]; then
|
||||
printf 'missing %s or its README.md\n' "$GOLDENS_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
family_count="$(find "$GOLDENS_DIR" -mindepth 1 -maxdepth 1 -type d | wc -l | tr -d ' ')"
|
||||
if [[ "$family_count" -ne "$EXPECTED_FAMILIES" ]]; then
|
||||
printf 'expected %s family directories under %s, found %s\n' "$EXPECTED_FAMILIES" "$GOLDENS_DIR" "$family_count" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
existing="$(find "$GOLDENS_DIR" -type f \( -name '*.out.xml' -o -name 'rustfs.in.xml' \) | sort)"
|
||||
if [[ -n "$existing" && "$force" -ne 1 ]]; then
|
||||
printf 'refusing: generated files already exist; pass --force to regenerate on purpose\n%s\n' "$existing" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
dirty="$(git status --porcelain -- crates/ecstore/src Cargo.toml Cargo.lock)"
|
||||
if [[ -n "$dirty" ]]; then
|
||||
printf 'refusing: codec-affecting paths have uncommitted changes, so the recorded commit would not describe the codec that produced the bytes\n%s\n' "$dirty" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log="$(mktemp -t bucket-config-goldens.XXXXXX)"
|
||||
trap 'rm -f "$log"' EXIT
|
||||
|
||||
if [[ -n "$existing" ]]; then
|
||||
printf 'removing previously generated files (--force)\n'
|
||||
printf '%s\n' "$existing" | xargs rm -f
|
||||
fi
|
||||
|
||||
printf 'writing goldens (every pair test fails on purpose in write mode)...\n'
|
||||
env "$WRITE_ENV=1" cargo nextest run -p rustfs-ecstore --test "$TEST_BINARY" --no-fail-fast >"$log" 2>&1 || true
|
||||
|
||||
out_count="$(find "$GOLDENS_DIR" -type f -name '*.out.xml' | wc -l | tr -d ' ')"
|
||||
in_count="$(find "$GOLDENS_DIR" -type f -name 'rustfs.in.xml' | wc -l | tr -d ' ')"
|
||||
if [[ "$out_count" -ne "$EXPECTED_PAIRS" || "$in_count" -ne "$EXPECTED_FAMILIES" ]]; then
|
||||
cat "$log" >&2
|
||||
printf 'write mode produced %s/%s .out.xml and %s/%s rustfs.in.xml files\n' \
|
||||
"$out_count" "$EXPECTED_PAIRS" "$in_count" "$EXPECTED_FAMILIES" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf 'verifying the generated goldens...\n'
|
||||
cargo nextest run -p rustfs-ecstore --test "$TEST_BINARY" 2>&1 | tee "$log"
|
||||
if ! grep -Eq "Summary.* ${EXPECTED_PAIRS} tests run: ${EXPECTED_PAIRS} passed" "$log"; then
|
||||
printf 'verification did not report %s passed\n' "$EXPECTED_PAIRS" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
commit="$(git rev-parse HEAD)"
|
||||
stamp="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
line="Generated at commit: \`$commit\` ($stamp)"
|
||||
tmp="$(mktemp -t bucket-config-goldens-readme.XXXXXX)"
|
||||
awk -v line="$line" '/^Generated at commit:/ { print line; next } { print }' "$README" >"$tmp"
|
||||
mv "$tmp" "$README"
|
||||
if ! grep -Fq "$line" "$README"; then
|
||||
printf 'failed to record the generating commit in %s\n' "$README" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf 'generated %s pairs across %s families at %s\n' "$EXPECTED_PAIRS" "$EXPECTED_FAMILIES" "$commit"
|
||||
Reference in New Issue
Block a user