This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion.git


The following commit(s) were added to refs/heads/main by this push:
     new e2ca7f3805 fix datafusion-cli CI: switch from minio to rustfs (#25706)
e2ca7f3805 is described below

commit e2ca7f38051744b2010cf09b80db8e9dfa4b5d38
Author: Andrew Duffy <[email protected]>
AuthorDate: Thu Sep 24 14:54:42 2026 +0000

    fix datafusion-cli CI: switch from minio to rustfs (#25706)
    
    ## Which issue does this PR close?
    
    Closes https://github.com/apache/datafusion/issues/25705
    
    ## Rationale for this change
    
    This morning it seems like Minio pulled its images from quay.io, which
    has caused recent CI runs to start failing, e.g.
    
    
    
https://github.com/apache/datafusion/actions/runs/36006680489/job/107657362588
    
    RustFS is a pretty comparable (and more permissively licensed)
    alternative that provides a suitable S3 interface for integration tests.
    
    This was substantially authored by Codex, but reviewed by me and tested
    locally.
    
    ## What is the testing strategy for this PR?
    
    Existing integration tests
    
    ## Are there any user-facing changes?
    
    No
    
    Signed-off-by: Andrew Duffy <[email protected]>
---
 .github/workflows/rust.yml              |  17 ++-
 datafusion-cli/CONTRIBUTING.md          |   6 +-
 datafusion-cli/Cargo.toml               |   2 +-
 datafusion-cli/tests/cli_integration.rs | 246 ++++++++++++++++++--------------
 4 files changed, 149 insertions(+), 122 deletions(-)

diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index b9b8ed22cc..b6c9114942 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -480,22 +480,21 @@ jobs:
         with:
           save-if: false # set in linux-test
           shared-key: "amd-ci"
-      # The storage integration tests start MinIO containers. Pulling the image
+      # The storage integration tests start RustFS containers. Pulling the 
image
       # once up front keeps the pull off the critical path of the tests, which
       # would otherwise pull it several times concurrently and occasionally 
fail
       # with transient Docker transport errors. The tests retry the pull
       # themselves, so a failure here is only a warning.
       #
-      # MINIO_IMAGE must match what the tests start: the tag comes from the
-      # `minio` module of `testcontainers-modules`, the registry from
-      # MINIO_IMAGE_NAME in `datafusion-cli/tests/cli_integration.rs`. The
-      # `minio_image_matches_ci_prepull` test fails if either drifts.
-      - name: Pre-pull MinIO image
+      # RUSTFS_IMAGE must match RUSTFS_IMAGE_NAME and RUSTFS_IMAGE_TAG in
+      # `datafusion-cli/tests/cli_integration.rs`. The
+      # `rustfs_image_matches_ci_prepull` test fails if they differ.
+      - name: Pre-pull RustFS image
         env:
-          MINIO_IMAGE: quay.io/minio/minio:RELEASE.2025-02-28T09-55-16Z
+          RUSTFS_IMAGE: docker.io/rustfs/rustfs:1.0.0
         run: |
-          ci/scripts/retry timeout 120 docker pull "$MINIO_IMAGE" \
-            || echo "::warning::Could not pre-pull $MINIO_IMAGE, the tests 
will pull it themselves"
+          ci/scripts/retry timeout 120 docker pull "$RUSTFS_IMAGE" \
+            || echo "::warning::Could not pre-pull $RUSTFS_IMAGE, the tests 
will pull it themselves"
       - name: Run tests (excluding doctests)
         env:
           RUST_BACKTRACE: 1
diff --git a/datafusion-cli/CONTRIBUTING.md b/datafusion-cli/CONTRIBUTING.md
index 8be656ec4e..fa615028a4 100644
--- a/datafusion-cli/CONTRIBUTING.md
+++ b/datafusion-cli/CONTRIBUTING.md
@@ -35,7 +35,7 @@ cargo test --all-targets
 
 ## Running Storage Integration Tests
 
-By default, storage integration tests are not run. These tests use the 
`testcontainers` crate to start up a local MinIO server using Docker on port 
9000.
+By default, storage integration tests are not run. These tests use the 
`testcontainers` crate to start up a local RustFS server using Docker with a 
dynamically assigned host port.
 
 To run them you will need to set `TEST_STORAGE_INTEGRATION`:
 
@@ -47,8 +47,8 @@ For some of the tests, 
[snapshots](https://datafusion.apache.org/contributor-gui
 
 ### AWS
 
-S3 integration is tested against [Minio](https://github.com/minio/minio) with 
[TestContainers](https://github.com/testcontainers/testcontainers-rs)
-This requires Docker to be running on your machine and port 9000 to be free.
+S3 integration is tested against [RustFS](https://github.com/rustfs/rustfs) 
with [TestContainers](https://github.com/testcontainers/testcontainers-rs)
+This requires Docker to be running on your machine.
 
 If you see an error mentioning "failed to load IMDS session token" such as
 
diff --git a/datafusion-cli/Cargo.toml b/datafusion-cli/Cargo.toml
index aa508123d6..51a6109a01 100644
--- a/datafusion-cli/Cargo.toml
+++ b/datafusion-cli/Cargo.toml
@@ -78,7 +78,7 @@ ctor = { workspace = true }
 insta = { workspace = true }
 insta-cmd = "0.7.0"
 rstest = { workspace = true }
-testcontainers-modules = { workspace = true, features = ["minio"] }
+testcontainers-modules = { workspace = true }
 # Makes sure `test_display_pg_json` behaves in a consistent way regardless of
 # feature unification with dependencies
 serde_json = { workspace = true, features = ["preserve_order"] }
diff --git a/datafusion-cli/tests/cli_integration.rs 
b/datafusion-cli/tests/cli_integration.rs
index a9209bc598..e0fedf0d5b 100644
--- a/datafusion-cli/tests/cli_integration.rs
+++ b/datafusion-cli/tests/cli_integration.rs
@@ -20,17 +20,22 @@ use std::process::Command;
 use rstest::rstest;
 
 use async_trait::async_trait;
+use futures::TryStreamExt;
 use insta::internals::SettingsBindDropGuard;
 use insta::{Settings, glob};
 use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
+use object_store::{
+    ObjectStore, ObjectStoreExt, aws::AmazonS3Builder, local::LocalFileSystem,
+};
 use std::path::PathBuf;
 use std::time::Duration;
 use std::{env, fs};
-use testcontainers_modules::minio;
-use testcontainers_modules::testcontainers::core::{CmdWaitFor, ExecCommand, 
Mount};
+use testcontainers_modules::testcontainers::core::{
+    CmdWaitFor, ExecCommand, IntoContainerPort,
+};
 use testcontainers_modules::testcontainers::runners::AsyncRunner;
 use testcontainers_modules::testcontainers::{
-    ContainerAsync, Image, ImageExt, TestcontainersError,
+    ContainerAsync, GenericImage, ImageExt, TestcontainersError,
 };
 
 fn cli() -> Command {
@@ -46,43 +51,39 @@ fn make_settings() -> Settings {
     settings
 }
 
-const MINIO_ROOT_USER: &str = "TEST-DataFusionLogin";
-const MINIO_ROOT_PASSWORD: &str = "TEST-DataFusionPassword";
+const RUSTFS_ACCESS_KEY: &str = "TEST-DataFusionLogin";
+const RUSTFS_SECRET_KEY: &str = "TEST-DataFusionPassword";
 
-/// Registry override for the image pinned by `testcontainers-modules`.
-///
-/// MinIO withdrew `minio/minio` from Docker Hub on 2026-09-11. quay.io still
-/// serves the same tag, so only the registry changes here. An unblock, not a
-/// fix: see <https://github.com/apache/datafusion/issues/25215>.
-const MINIO_IMAGE_NAME: &str = "quay.io/minio/minio";
+/// Pinned RustFS image, also pulled by the CLI CI job.
+const RUSTFS_IMAGE_NAME: &str = "docker.io/rustfs/rustfs";
+const RUSTFS_IMAGE_TAG: &str = "1.0.0";
 
-/// How many times to try bringing up the MinIO container before failing.
+/// How many times to try bringing up the RustFS container before failing.
 ///
-/// Both the image pull and the `mc` calls that provision the bucket fail
-/// intermittently on CI with transient errors such as
-/// `bytes remaining on stream`. Retrying is much cheaper than a flaky run.
-const MINIO_SETUP_ATTEMPTS: u32 = 3;
+/// Image pulls and fixture uploads can fail with transient errors such as
+/// `bytes remaining on stream`. Retry these failures before failing the test.
+const RUSTFS_SETUP_ATTEMPTS: u32 = 3;
 
-/// Delay before the first retry of the MinIO setup, doubled on each attempt.
-const MINIO_SETUP_RETRY_DELAY: Duration = Duration::from_secs(5);
+/// Delay before the first retry of the RustFS setup, doubled on each attempt.
+const RUSTFS_SETUP_RETRY_DELAY: Duration = Duration::from_secs(5);
 
-/// Time budget for a single MinIO setup attempt. A stalled image pull or `mc`
+/// Time budget for a single RustFS setup attempt. A stalled image pull or 
`curl`
 /// invocation is retried instead of hanging the whole test run.
-const MINIO_SETUP_TIMEOUT: Duration = Duration::from_mins(3);
+const RUSTFS_SETUP_TIMEOUT: Duration = Duration::from_mins(3);
 
-/// Starts a MinIO container preloaded with the test data, retrying transient
+/// Starts a RustFS container preloaded with the test data, retrying transient
 /// Docker failures.
 ///
 /// Returns `None` when the test should be skipped, that is when
 /// `TEST_STORAGE_INTEGRATION` is unset or the registry is rate limiting the
 /// image pull. Panics if the container cannot be started for any other reason.
-async fn start_minio_or_skip() -> Option<ContainerAsync<minio::MinIO>> {
+async fn start_rustfs_or_skip() -> Option<ContainerAsync<GenericImage>> {
     if env::var("TEST_STORAGE_INTEGRATION").is_err() {
         eprintln!("Skipping external storages integration tests");
         return None;
     }
 
-    match setup_minio_container().await {
+    match setup_rustfs_container().await {
         Ok(container) => Some(container),
         Err(e) if is_docker_pull_rate_limit(&e) => {
             eprintln!("Skipping test: Docker pull rate limit reached: {e}");
@@ -105,30 +106,30 @@ fn is_retryable(error: &str) -> bool {
         && !error.contains("failed to initialize a docker client")
 }
 
-async fn setup_minio_container() -> Result<ContainerAsync<minio::MinIO>, 
String> {
-    let mut delay = MINIO_SETUP_RETRY_DELAY;
-    let mut last_error = String::from("MinIO container setup was not attempted 
at all");
+async fn setup_rustfs_container() -> Result<ContainerAsync<GenericImage>, 
String> {
+    let mut delay = RUSTFS_SETUP_RETRY_DELAY;
+    let mut last_error = String::from("RustFS container setup was not 
attempted at all");
 
-    for attempt in 1..=MINIO_SETUP_ATTEMPTS {
+    for attempt in 1..=RUSTFS_SETUP_ATTEMPTS {
         last_error = match tokio::time::timeout(
-            MINIO_SETUP_TIMEOUT,
-            try_setup_minio_container(),
+            RUSTFS_SETUP_TIMEOUT,
+            try_setup_rustfs_container(),
         )
         .await
         {
             Ok(Ok(container)) => return Ok(container),
             Ok(Err(e)) => e,
             Err(_) => format!(
-                "Timed out after {MINIO_SETUP_TIMEOUT:?} while starting the 
MinIO container"
+                "Timed out after {RUSTFS_SETUP_TIMEOUT:?} while starting the 
RustFS container"
             ),
         };
 
-        if attempt == MINIO_SETUP_ATTEMPTS || !is_retryable(&last_error) {
+        if attempt == RUSTFS_SETUP_ATTEMPTS || !is_retryable(&last_error) {
             break;
         }
 
         eprintln!(
-            "MinIO container setup failed (attempt 
{attempt}/{MINIO_SETUP_ATTEMPTS}), \
+            "RustFS container setup failed (attempt 
{attempt}/{RUSTFS_SETUP_ATTEMPTS}), \
              retrying in {delay:?}: {last_error}"
         );
         tokio::time::sleep(delay).await;
@@ -138,69 +139,76 @@ async fn setup_minio_container() -> 
Result<ContainerAsync<minio::MinIO>, String>
     Err(last_error)
 }
 
-/// A single attempt at starting and provisioning a MinIO container.
+/// A single attempt at starting and provisioning a RustFS container.
 ///
 /// The container is removed again if provisioning fails, so that the next
 /// attempt starts from a clean state.
-async fn try_setup_minio_container() -> Result<ContainerAsync<minio::MinIO>, 
String> {
-    let container = start_minio_container().await?;
+async fn try_setup_rustfs_container() -> Result<ContainerAsync<GenericImage>, 
String> {
+    let container = start_rustfs_container().await?;
 
-    match provision_minio_container(&container).await {
+    match provision_rustfs_container(&container).await {
         Ok(()) => Ok(container),
         Err(e) => {
             if let Err(rm_error) = container.rm().await {
-                eprintln!("Failed to remove the MinIO container: {rm_error}");
+                eprintln!("Failed to remove the RustFS container: {rm_error}");
             }
             Err(e)
         }
     }
 }
 
-async fn start_minio_container() -> Result<ContainerAsync<minio::MinIO>, 
String> {
-    let data_path =
-        
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../datafusion/core/tests/data");
-
-    let absolute_data_path = data_path
-        .canonicalize()
-        .expect("Failed to get absolute path for test data");
-
-    minio::MinIO::default()
-        .with_name(MINIO_IMAGE_NAME)
-        .with_env_var("MINIO_ROOT_USER", MINIO_ROOT_USER)
-        .with_env_var("MINIO_ROOT_PASSWORD", MINIO_ROOT_PASSWORD)
-        .with_mount(Mount::bind_mount(
-            absolute_data_path.to_str().unwrap(),
-            "/source",
-        ))
+async fn start_rustfs_container() -> Result<ContainerAsync<GenericImage>, 
String> {
+    GenericImage::new(RUSTFS_IMAGE_NAME, RUSTFS_IMAGE_TAG)
+        .with_exposed_port(9000.tcp())
+        .with_env_var("RUSTFS_ACCESS_KEY", RUSTFS_ACCESS_KEY)
+        .with_env_var("RUSTFS_SECRET_KEY", RUSTFS_SECRET_KEY)
+        .with_env_var("RUSTFS_CONSOLE_ENABLE", "false")
         .start()
         .await
         .map_err(|e| match e {
             TestcontainersError::Client(e) => format!(
-                "Failed to start MinIO container. Ensure Docker is running and 
accessible: {e}"
+                "Failed to start RustFS container. Ensure Docker is running 
and accessible: {e}"
             ),
-            e => format!("Failed to start MinIO container: {e}"),
+            e => format!("Failed to start RustFS container: {e}"),
         })
 }
 
-/// Waits for MinIO to be healthy and uploads the test files.
+/// Waits for RustFS to be healthy and uploads the test files.
 ///
-/// This is done via the `mc` CLI shipped in the image to avoid an s3 
dependency.
-async fn provision_minio_container(
-    container: &ContainerAsync<minio::MinIO>,
+/// Use the image's `curl` to check readiness and create the bucket, then 
upload
+/// the fixtures through `object_store`.
+async fn provision_rustfs_container(
+    container: &ContainerAsync<GenericImage>,
 ) -> Result<(), String> {
+    let credentials = format!("{RUSTFS_ACCESS_KEY}:{RUSTFS_SECRET_KEY}");
     let commands = [
-        ExecCommand::new(["/usr/bin/mc", "ready", "local"]),
         ExecCommand::new([
-            "/usr/bin/mc",
-            "alias",
-            "set",
-            "localminio",
-            "http://localhost:9000";,
-            MINIO_ROOT_USER,
-            MINIO_ROOT_PASSWORD,
+            "curl",
+            "--fail",
+            "--silent",
+            "--show-error",
+            "--retry",
+            "60",
+            "--retry-delay",
+            "1",
+            "--retry-all-errors",
+            "--max-time",
+            "5",
+            "http://localhost:9000/health/ready";,
+        ]),
+        ExecCommand::new([
+            "curl",
+            "--fail",
+            "--silent",
+            "--show-error",
+            "--aws-sigv4",
+            "aws:amz:us-east-1:s3",
+            "--user",
+            &credentials,
+            "--request",
+            "PUT",
+            "http://localhost:9000/data";,
         ]),
-        ExecCommand::new(["/usr/bin/mc", "mb", "localminio/data"]),
-        ExecCommand::new(["/usr/bin/mc", "cp", "-r", "/source/", 
"localminio/data/"]),
     ];
 
     for command in commands {
@@ -223,14 +231,45 @@ async fn provision_minio_container(
         }
     }
 
+    let port = container
+        .get_host_port_ipv4(9000)
+        .await
+        .map_err(|e| e.to_string())?;
+    let store = AmazonS3Builder::new()
+        .with_bucket_name("data")
+        .with_region("us-east-1")
+        .with_access_key_id(RUSTFS_ACCESS_KEY)
+        .with_secret_access_key(RUSTFS_SECRET_KEY)
+        .with_endpoint(format!("http://localhost:{port}";))
+        .with_allow_http(true)
+        .build()
+        .map_err(|e| e.to_string())?;
+    let data_path =
+        
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../datafusion/core/tests/data");
+    let source =
+        LocalFileSystem::new_with_prefix(data_path).map_err(|e| 
e.to_string())?;
+    let mut files = source.list(None);
+    while let Some(file) = files.try_next().await.map_err(|e| e.to_string())? {
+        let data = source
+            .get(&file.location)
+            .await
+            .map_err(|e| e.to_string())?
+            .bytes()
+            .await
+            .map_err(|e| e.to_string())?;
+        store
+            .put(&file.location, data.into())
+            .await
+            .map_err(|e| e.to_string())?;
+    }
+
     Ok(())
 }
 
-/// CI pre-pulls the MinIO image so that the storage integration tests do not
-/// have to pull it themselves. Guard against that pre-pull going stale when
-/// `testcontainers-modules` bumps the image it uses.
+/// CI pre-pulls the RustFS image so that the storage integration tests do not
+/// have to pull it themselves. Keep the CI image and the test image in sync.
 #[test]
-fn minio_image_matches_ci_prepull() {
+fn rustfs_image_matches_ci_prepull() {
     let workflow =
         
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../.github/workflows/rust.yml");
 
@@ -239,25 +278,12 @@ fn minio_image_matches_ci_prepull() {
         return;
     };
 
-    let image = minio::MinIO::default();
+    let image_ref = format!("{RUSTFS_IMAGE_NAME}:{RUSTFS_IMAGE_TAG}");
 
-    // The override only redirects the registry, so it would silently stop
-    // tracking upstream if the crate ever pinned a different image.
     assert!(
-        MINIO_IMAGE_NAME.ends_with(&format!("/{}", image.name())),
-        "`testcontainers-modules` now uses `{}`, which MINIO_IMAGE_NAME \
-         (`{MINIO_IMAGE_NAME}`) no longer mirrors.",
-        image.name()
-    );
-
-    // Match the assignment: `minio/minio:<tag>` is a substring of the quay
-    // reference and would pass either way.
-    let image_ref = format!("{MINIO_IMAGE_NAME}:{}", image.tag());
-
-    assert!(
-        contents.contains(&format!("MINIO_IMAGE: {image_ref}")),
-        "{} does not pre-pull `{image_ref}`. Update MINIO_IMAGE in the \
-         `Pre-pull MinIO image` step to match the image used by the tests.",
+        contents.contains(&format!("RUSTFS_IMAGE: {image_ref}")),
+        "{} does not pre-pull `{image_ref}`. Update RUSTFS_IMAGE in the \
+         `Pre-pull RustFS image` step to match the image used by the tests.",
         workflow.display()
     );
 }
@@ -695,7 +721,7 @@ fn test_cli_wide_result_set_no_crash() {
 
 #[tokio::test]
 async fn test_cli() {
-    let Some(container) = start_minio_or_skip().await else {
+    let Some(container) = start_rustfs_or_skip().await else {
         return;
     };
 
@@ -709,8 +735,8 @@ async fn test_cli() {
         assert_cmd_snapshot!(
             cli()
                 .env_clear()
-                .env("AWS_ACCESS_KEY_ID", MINIO_ROOT_USER)
-                .env("AWS_SECRET_ACCESS_KEY", MINIO_ROOT_PASSWORD)
+                .env("AWS_ACCESS_KEY_ID", RUSTFS_ACCESS_KEY)
+                .env("AWS_SECRET_ACCESS_KEY", RUSTFS_SECRET_KEY)
                 .env("AWS_ENDPOINT", format!("http://localhost:{port}";))
                 .env("AWS_ALLOW_HTTP", "true")
                 .pass_stdin(input)
@@ -722,7 +748,7 @@ async fn test_cli() {
 async fn test_aws_options() {
     // Separate test is needed to pass aws as options in sql and not via env
 
-    let Some(container) = start_minio_or_skip().await else {
+    let Some(container) = start_rustfs_or_skip().await else {
         return;
     };
 
@@ -736,8 +762,8 @@ async fn test_aws_options() {
 STORED AS CSV
 LOCATION 's3://data/cars.csv'
 OPTIONS(
-    'aws.access_key_id' '{MINIO_ROOT_USER}',
-    'aws.secret_access_key' '{MINIO_ROOT_PASSWORD}',
+    'aws.access_key_id' '{RUSTFS_ACCESS_KEY}',
+    'aws.secret_access_key' '{RUSTFS_SECRET_KEY}',
     'aws.endpoint' 'http://localhost:{port}',
     'aws.allow_http' 'true'
 );
@@ -812,7 +838,7 @@ fn test_backtrace_output(#[case] query: &str) {
 
 #[tokio::test]
 async fn test_s3_url_fallback() {
-    let Some(container) = start_minio_or_skip().await else {
+    let Some(container) = start_rustfs_or_skip().await else {
         return;
     };
 
@@ -833,13 +859,13 @@ OPTIONS (
 SELECT * FROM partitioned_data ORDER BY column_1, column_2 LIMIT 5;
 "#;
 
-    assert_cmd_snapshot!(cli().with_minio(&container).await.pass_stdin(input));
+    
assert_cmd_snapshot!(cli().with_rustfs(&container).await.pass_stdin(input));
 }
 
 /// Validate object store profiling output
 #[tokio::test]
 async fn test_object_store_profiling() {
-    let Some(container) = start_minio_or_skip().await else {
+    let Some(container) = start_rustfs_or_skip().await else {
         return;
     };
 
@@ -885,27 +911,29 @@ SELECT * from CARS LIMIT 1;
 SELECT * from CARS LIMIT 1;
 "#;
 
-    assert_cmd_snapshot!(cli().with_minio(&container).await.pass_stdin(input));
+    
assert_cmd_snapshot!(cli().with_rustfs(&container).await.pass_stdin(input));
 }
 
-/// Extension trait to Add the minio connection information to a Command
+/// Add the RustFS connection information to a Command.
 #[async_trait]
-trait MinioCommandExt {
-    async fn with_minio(&mut self, container: &ContainerAsync<minio::MinIO>)
-    -> &mut Self;
+trait RustfsCommandExt {
+    async fn with_rustfs(
+        &mut self,
+        container: &ContainerAsync<GenericImage>,
+    ) -> &mut Self;
 }
 
 #[async_trait]
-impl MinioCommandExt for Command {
-    async fn with_minio(
+impl RustfsCommandExt for Command {
+    async fn with_rustfs(
         &mut self,
-        container: &ContainerAsync<minio::MinIO>,
+        container: &ContainerAsync<GenericImage>,
     ) -> &mut Self {
         let port = container.get_host_port_ipv4(9000).await.unwrap();
 
         self.env_clear()
-            .env("AWS_ACCESS_KEY_ID", MINIO_ROOT_USER)
-            .env("AWS_SECRET_ACCESS_KEY", MINIO_ROOT_PASSWORD)
+            .env("AWS_ACCESS_KEY_ID", RUSTFS_ACCESS_KEY)
+            .env("AWS_SECRET_ACCESS_KEY", RUSTFS_SECRET_KEY)
             .env("AWS_ENDPOINT", format!("http://localhost:{port}";))
             .env("AWS_ALLOW_HTTP", "true")
     }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to