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

kevinjqliu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-go.git


The following commit(s) were added to refs/heads/main by this push:
     new a2b91aee2 ci: replace MinIO with RustFS in integration tests (#2055)
a2b91aee2 is described below

commit a2b91aee208daeffc3d955d5bb54a549a9149df6
Author: Sreesh Maheshwar <[email protected]>
AuthorDate: Thu Sep 24 23:43:39 2026 +0100

    ci: replace MinIO with RustFS in integration tests (#2055)
    
    * ci: replace MinIO with RustFS in integration tests
    
    Use RustFS 1.0.0 and AWS CLI bucket setup for the shared S3 test fixture.
    
    Generated-by: Codex
    Signed-off-by: Sreesh Maheshwar <[email protected]>
    
    * ci: apply the shared S3 configuration to Spark 3.5
    
    Copy spark-defaults.conf into the Spark 3.5 image so it uses object-store 
instead of the inherited MinIO endpoint.
    
    Generated-by: Codex
    Signed-off-by: Sreesh Maheshwar <[email protected]>
    
    * ci: preserve the Spark 3.5 image catalog configuration
    
    Override only the inherited S3 endpoint, retaining the demo catalog and 
history-server settings.
    
    Generated-by: Codex
    Signed-off-by: Sreesh Maheshwar <[email protected]>
    
    * ci: make RustFS fixture startup reliable and observable
    
    Expose server logs, retry bucket initialization, and rebuild the Spark 
fixture so local images pick up its endpoint configuration.
    
    Generated-by: Codex
    Signed-off-by: Sreesh Maheshwar <[email protected]>
    
    * ci: empty the warehouse bucket on setup
    
    Clear any existing objects in the warehouse bucket during create-bucket
    so re-running integration setup starts from a clean state, as before.
    Also update a leftover object store reference in the CLI docs.
    
    Co-authored-by: Copilot App <[email protected]>
    
    * ci: share spark-defaults.conf with the Spark 3.5 image
    
    Copy the repo's spark-defaults.conf into the Spark 3.5 image, as the
    Spark 4 image already does, so both use one catalog configuration
    instead of rewriting the base image's config.
    
    Co-authored-by: Copilot App <[email protected]>
    
    ---------
    
    Signed-off-by: Sreesh Maheshwar <[email protected]>
    Co-authored-by: Kevin Liu <[email protected]>
    Co-authored-by: Copilot App <[email protected]>
---
 .github/workflows/go-integration.yml               |  2 +-
 CONTRIBUTING.md                                    |  2 +-
 Makefile                                           |  4 +-
 README.md                                          |  2 +-
 catalog/README.md                                  |  8 +--
 ...ation_test.go => hadoop_s3_integration_test.go} | 36 ++++++------
 internal/recipe/Dockerfile                         |  1 +
 internal/recipe/docker-compose.yml                 | 68 ++++++++++++++--------
 internal/recipe/provision.py                       |  2 +-
 internal/recipe/spark-defaults.conf                |  2 +-
 io/gocloud/s3/s3_integration_test.go               |  4 +-
 website/src/cli.md                                 |  2 +-
 12 files changed, 76 insertions(+), 57 deletions(-)

diff --git a/.github/workflows/go-integration.yml 
b/.github/workflows/go-integration.yml
index 818f5867a..5e77bd377 100644
--- a/.github/workflows/go-integration.yml
+++ b/.github/workflows/go-integration.yml
@@ -70,7 +70,7 @@ jobs:
 
       - name: Setup environment variables
         run: |
-          echo "AWS_S3_ENDPOINT=http://$(docker inspect -f 
'{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' minio):9000" >> 
$GITHUB_ENV
+          echo "AWS_S3_ENDPOINT=http://$(docker inspect -f 
'{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' object-store):9000" 
>> $GITHUB_ENV
           echo "SPARK_CONTAINER_ID=$(docker ps -qf 'name=spark-iceberg')" >> 
$GITHUB_ENV
           echo "DOCKER_API_VERSION=$(docker version -f 
'{{.Server.APIVersion}}')" >> $GITHUB_ENV
 
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index bad108f58..c81b48983 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -60,6 +60,6 @@ make test-race # runs all unit tests with race detector
 Integration tests require Docker and are gated behind a build tag:
 
 ```bash
-docker compose -f internal/recipe/docker-compose.yml up -d rest minio mc --wait
+docker compose -f internal/recipe/docker-compose.yml up -d rest object-store 
create-bucket --wait
 go test -tags integration ./...
 ```
diff --git a/Makefile b/Makefile
index c19a48dfb..741393ac7 100644
--- a/Makefile
+++ b/Makefile
@@ -39,7 +39,7 @@ lint-install:
 
 integration-setup:
        mkdir -p /tmp/iceberg-hadoop-warehouse
-       docker compose -f internal/recipe/docker-compose.yml up -d --wait
+       docker compose -f internal/recipe/docker-compose.yml up -d --build 
--wait
        docker compose -f internal/recipe/docker-compose.yml exec -T 
spark-iceberg ipython ./provision.py
 
 integration-setup-spark4:
@@ -55,7 +55,7 @@ integration-logs:
        docker compose -f internal/recipe/docker-compose.yml logs
 
 integration-env:
-       @echo "export AWS_S3_ENDPOINT=http://$$(docker inspect -f 
'{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' minio):9000"
+       @echo "export AWS_S3_ENDPOINT=http://$$(docker inspect -f 
'{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' object-store):9000"
        @echo "export AWS_REGION=us-east-1"
        @echo "export SPARK_CONTAINER_ID=$$(docker ps -qf 'name=spark-iceberg')"
        @echo "export DOCKER_API_VERSION=$$(docker version -f 
'{{.Server.APIVersion}}')"
diff --git a/README.md b/README.md
index 23888222e..23414a28b 100644
--- a/README.md
+++ b/README.md
@@ -70,7 +70,7 @@ make lint-install
 2. Export the required environment variables:
 
    ```shell
-   export AWS_S3_ENDPOINT=http://$(docker inspect -f 
'{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' minio):9000
+   export AWS_S3_ENDPOINT=http://$(docker inspect -f 
'{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' object-store):9000
    export AWS_REGION=us-east-1
    export SPARK_CONTAINER_ID=$(docker ps -qf 'name=spark-iceberg')
    export DOCKER_API_VERSION=$(docker version -f '{{.Server.APIVersion}}')
diff --git a/catalog/README.md b/catalog/README.md
index 37912d27d..265d66715 100644
--- a/catalog/README.md
+++ b/catalog/README.md
@@ -25,11 +25,11 @@ in the `cmd/iceberg` folder.
 ### REST Catalog
 
 To test the REST catalog implementation, we have a docker configuration
-for a Minio container and tabulario/iceberg-rest container.
+for RustFS and the Apache Iceberg REST fixture.
 
-You can spin up the local catalog by going to the `dev/` folder and running
-`docker-compose up`. You can then follow the steps of the Iceberg 
[Quickstart](https://iceberg.apache.org/spark-quickstart/#creating-a-table) 
-tutorial, which we've summarized below.
+You can spin up the local catalog from the repository root by running
+`make integration-setup`. You can then follow the steps of the Iceberg
+[Quickstart](https://iceberg.apache.org/spark-quickstart/#creating-a-table) 
tutorial, which we've summarized below.
 
 #### Setup your Iceberg catalog
 
diff --git a/catalog/hadoop/hadoop_minio_integration_test.go 
b/catalog/hadoop/hadoop_s3_integration_test.go
similarity index 84%
rename from catalog/hadoop/hadoop_minio_integration_test.go
rename to catalog/hadoop/hadoop_s3_integration_test.go
index 7d69e1141..184282799 100644
--- a/catalog/hadoop/hadoop_minio_integration_test.go
+++ b/catalog/hadoop/hadoop_s3_integration_test.go
@@ -36,7 +36,7 @@ import (
        "github.com/stretchr/testify/suite"
 )
 
-type HadoopMinIOIntegrationSuite struct {
+type HadoopS3IntegrationSuite struct {
        suite.Suite
 
        ctx       context.Context
@@ -45,9 +45,9 @@ type HadoopMinIOIntegrationSuite struct {
        warehouse string
 }
 
-func (s *HadoopMinIOIntegrationSuite) SetupTest() {
+func (s *HadoopS3IntegrationSuite) SetupTest() {
        s.ctx = context.Background()
-       s.warehouse = fmt.Sprintf("s3a://warehouse/hadoop-minio/%s", 
uuid.NewString())
+       s.warehouse = fmt.Sprintf("s3a://warehouse/hadoop-s3/%s", 
uuid.NewString())
        s.props = iceberg.Properties{
                "allow-unsafe-commits":      "true",
                icebergio.S3Region:          "local",
@@ -57,12 +57,12 @@ func (s *HadoopMinIOIntegrationSuite) SetupTest() {
                // from `make integration-env` or recipe.Start.
        }
 
-       cat, err := hadoop.NewCatalog("hadoop_minio_test", s.warehouse, s.props)
+       cat, err := hadoop.NewCatalog("hadoop_s3_test", s.warehouse, s.props)
        s.Require().NoError(err)
        s.cat = cat
 }
 
-func (s *HadoopMinIOIntegrationSuite) testSchema() *iceberg.Schema {
+func (s *HadoopS3IntegrationSuite) testSchema() *iceberg.Schema {
        return iceberg.NewSchema(
                1,
                iceberg.NestedField{ID: 1, Name: "id", Type: 
iceberg.PrimitiveTypes.Int64, Required: true},
@@ -70,7 +70,7 @@ func (s *HadoopMinIOIntegrationSuite) testSchema() 
*iceberg.Schema {
        )
 }
 
-func (s *HadoopMinIOIntegrationSuite) TearDownTest() {
+func (s *HadoopS3IntegrationSuite) TearDownTest() {
        fs, err := icebergio.LoadFS(s.ctx, s.props, s.warehouse)
        if err != nil {
                return
@@ -81,9 +81,9 @@ func (s *HadoopMinIOIntegrationSuite) TearDownTest() {
        }
 }
 
-func (s *HadoopMinIOIntegrationSuite) TestNamespaceTableRoundTrip() {
-       ns := table.Identifier{"minio_ns"}
-       ident := table.Identifier{"minio_ns", "tbl"}
+func (s *HadoopS3IntegrationSuite) TestNamespaceTableRoundTrip() {
+       ns := table.Identifier{"s3_ns"}
+       ident := table.Identifier{"s3_ns", "tbl"}
 
        s.Require().NoError(s.cat.CreateNamespace(s.ctx, ns, nil))
 
@@ -94,7 +94,7 @@ func (s *HadoopMinIOIntegrationSuite) 
TestNamespaceTableRoundTrip() {
        tbl, err := s.cat.CreateTable(s.ctx, ident, s.testSchema())
        s.Require().NoError(err)
        s.NotNil(tbl)
-       s.True(strings.HasPrefix(tbl.MetadataLocation(), 
s.warehouse+"/minio_ns/tbl/metadata/v1.metadata.json"))
+       s.True(strings.HasPrefix(tbl.MetadataLocation(), 
s.warehouse+"/s3_ns/tbl/metadata/v1.metadata.json"))
 
        loaded, err := s.cat.LoadTable(s.ctx, ident)
        s.Require().NoError(err)
@@ -115,7 +115,7 @@ func (s *HadoopMinIOIntegrationSuite) 
TestNamespaceTableRoundTrip() {
        s.False(tableExists)
 }
 
-func (s *HadoopMinIOIntegrationSuite) TestListTablesEmptyNamespace() {
+func (s *HadoopS3IntegrationSuite) TestListTablesEmptyNamespace() {
        ns := table.Identifier{"empty_ns"}
        s.Require().NoError(s.cat.CreateNamespace(s.ctx, ns, nil))
 
@@ -127,7 +127,7 @@ func (s *HadoopMinIOIntegrationSuite) 
TestListTablesEmptyNamespace() {
        s.Empty(tables)
 }
 
-func (s *HadoopMinIOIntegrationSuite) TestListTablesMissingNamespace() {
+func (s *HadoopS3IntegrationSuite) TestListTablesMissingNamespace() {
        for _, err := range s.cat.ListTables(s.ctx, 
table.Identifier{"missing_ns"}) {
                s.ErrorIs(err, catalog.ErrNoSuchNamespace)
 
@@ -135,7 +135,7 @@ func (s *HadoopMinIOIntegrationSuite) 
TestListTablesMissingNamespace() {
        }
 }
 
-func (s *HadoopMinIOIntegrationSuite) 
TestListTablesOnlyDirectNamespaceTables() {
+func (s *HadoopS3IntegrationSuite) TestListTablesOnlyDirectNamespaceTables() {
        parent := table.Identifier{"parent_ns"}
        child := table.Identifier{"parent_ns", "child_ns"}
        directTable := table.Identifier{"parent_ns", "direct_tbl"}
@@ -156,7 +156,7 @@ func (s *HadoopMinIOIntegrationSuite) 
TestListTablesOnlyDirectNamespaceTables()
        s.Equal([]table.Identifier{directTable}, tables)
 }
 
-func (s *HadoopMinIOIntegrationSuite) 
TestDropTableRemovesTableFromListingAndLoad() {
+func (s *HadoopS3IntegrationSuite) 
TestDropTableRemovesTableFromListingAndLoad() {
        ns := table.Identifier{"drop_ns"}
        ident := table.Identifier{"drop_ns", "tbl"}
 
@@ -177,7 +177,7 @@ func (s *HadoopMinIOIntegrationSuite) 
TestDropTableRemovesTableFromListingAndLoa
        s.Empty(tables)
 }
 
-func (s *HadoopMinIOIntegrationSuite) TestPurgeTableRemovesTableRoot() {
+func (s *HadoopS3IntegrationSuite) TestPurgeTableRemovesTableRoot() {
        ns := table.Identifier{"purge_ns"}
        ident := table.Identifier{"purge_ns", "tbl"}
 
@@ -209,7 +209,7 @@ func (s *HadoopMinIOIntegrationSuite) 
TestPurgeTableRemovesTableRoot() {
        s.False(exists)
 }
 
-func (s *HadoopMinIOIntegrationSuite) TestDropNamespaceAfterDropTable() {
+func (s *HadoopS3IntegrationSuite) TestDropNamespaceAfterDropTable() {
        ns := table.Identifier{"drop_table_then_ns"}
        ident := table.Identifier{"drop_table_then_ns", "tbl"}
 
@@ -225,6 +225,6 @@ func (s *HadoopMinIOIntegrationSuite) 
TestDropNamespaceAfterDropTable() {
        s.Require().NoError(s.cat.DropNamespace(s.ctx, ns))
 }
 
-func TestHadoopIntegrationMinIO(t *testing.T) {
-       suite.Run(t, new(HadoopMinIOIntegrationSuite))
+func TestHadoopIntegrationS3(t *testing.T) {
+       suite.Run(t, new(HadoopS3IntegrationSuite))
 }
diff --git a/internal/recipe/Dockerfile b/internal/recipe/Dockerfile
index 073a276b1..e2ef77bc6 100644
--- a/internal/recipe/Dockerfile
+++ b/internal/recipe/Dockerfile
@@ -19,6 +19,7 @@ RUN pip3 install -q ipython
 RUN pip3 install pyiceberg[s3fs,hive]
 RUN pip3 install pyarrow
 
+COPY spark-defaults.conf ${SPARK_HOME}/conf/spark-defaults.conf
 COPY provision.py .
 COPY validation.py .
 COPY hadoop_validation.py .
diff --git a/internal/recipe/docker-compose.yml 
b/internal/recipe/docker-compose.yml
index b5328f1ab..be0ac0b6c 100644
--- a/internal/recipe/docker-compose.yml
+++ b/internal/recipe/docker-compose.yml
@@ -25,7 +25,7 @@ services:
       iceberg_net:
     depends_on:
       - rest
-      - minio
+      - object-store
     volumes:
       - ./warehouse:/home/iceberg/warehouse
       - ./notebooks:/home/iceberg/notebooks/notebooks
@@ -46,6 +46,9 @@ services:
       retries: 12
       start_period: 30s
   rest:
+    depends_on:
+      create-bucket:
+        condition: service_completed_successfully
     image: apache/iceberg-rest-fixture:1.10.1
     container_name: iceberg-rest
     networks:
@@ -58,8 +61,11 @@ services:
       - AWS_REGION=us-east-1
       - CATALOG_WAREHOUSE=s3://warehouse/
       - CATALOG_IO__IMPL=org.apache.iceberg.aws.s3.S3FileIO
-      - CATALOG_S3_ENDPOINT=http://minio:9000
+      - CATALOG_S3_ENDPOINT=http://object-store:9000
   rest-scan-planning:
+    depends_on:
+      create-bucket:
+        condition: service_completed_successfully
     # 1.10.1 predates the fixture's scan-planning implementation. Pin the
     # multi-platform image built from apache/iceberg@b0df3ca so the Java wire
     # compatibility tests are reproducible.
@@ -78,26 +84,32 @@ services:
       - AWS_REGION=us-east-1
       - CATALOG_WAREHOUSE=s3://warehouse/
       - CATALOG_IO__IMPL=org.apache.iceberg.aws.s3.S3FileIO
-      - CATALOG_S3_ENDPOINT=http://minio:9000
+      - CATALOG_S3_ENDPOINT=http://object-store:9000
       - CATALOG_INCLUDE__CREDENTIALS=true
       # Use a non-S3 credential so vending can be asserted without changing the
-      # static MinIO credentials used by the fixture's backing FileIO.
+      # static S3 credentials used by the fixture's backing FileIO.
       - CATALOG_GCS_OAUTH2_TOKEN=scan-planning-integration-token
-  minio:
-    image: quay.io/minio/minio
-    container_name: minio
+  object-store:
+    image: rustfs/rustfs:1.0.0
+    container_name: object-store
     environment:
-      - MINIO_ROOT_USER=admin
-      - MINIO_ROOT_PASSWORD=password
-      - MINIO_DOMAIN=minio
+      - RUSTFS_ACCESS_KEY=admin
+      - RUSTFS_SECRET_KEY=password
+      - RUSTFS_SERVER_DOMAINS=object-store
+      - RUSTFS_OBS_LOG_STDOUT_ENABLED=true
     networks:
       iceberg_net:
         aliases:
-          - warehouse.minio
+          - warehouse.object-store
     ports:
       - 9001:9001
       - 9000:9000
-    command: ["server", "/data", "--console-address", ":9001"]
+    command: ["/data"]
+    healthcheck:
+      test: ["CMD", "curl", "--fail", "http://localhost:9000/health/ready";]
+      interval: 5s
+      timeout: 5s
+      retries: 12
   azurite:
     image: mcr.microsoft.com/azure-storage/azurite
     container_name: azurite
@@ -105,25 +117,31 @@ services:
     ports:
       - 11000:11000
     command: ["azurite-blob", "--loose", "--blobHost", "0.0.0.0" 
,"--blobPort", "11000", "--skipApiVersionCheck"]
-  mc:
+  create-bucket:
     depends_on:
-      - minio
-    image: quay.io/minio/mc
-    container_name: mc
+      object-store:
+        condition: service_healthy
+    image: amazon/aws-cli:2.36.44
     networks:
       iceberg_net:
     environment:
       - AWS_ACCESS_KEY_ID=admin
       - AWS_SECRET_ACCESS_KEY=password
-      - AWS_REGION=us-east-1
-    entrypoint: >
-      /bin/sh -c "
-      until (/usr/bin/mc alias set minio http://minio:9000 admin password) do 
echo '...waiting...' && sleep 1; done;
-      /usr/bin/mc rm -r --force minio/warehouse;
-      /usr/bin/mc mb minio/warehouse;
-      /usr/bin/mc policy set public minio/warehouse;
-      tail -f /dev/null
-      "
+      - AWS_DEFAULT_REGION=us-east-1
+      - AWS_ENDPOINT_URL=http://object-store:9000
+    entrypoint: /bin/sh
+    command:
+      - -ec
+      - |
+        for attempt in 1 2 3 4 5; do
+          if { aws s3api head-bucket --bucket warehouse >/dev/null 2>&1 || aws 
s3api create-bucket --bucket warehouse; } \
+            && aws s3 rm s3://warehouse --recursive --quiet; then
+            exit 0
+          fi
+          sleep 2
+        done
+        echo "Failed to create warehouse bucket." >&2
+        exit 1
   clickhouse:
     image: clickhouse/clickhouse-server
     container_name: clickhouse
diff --git a/internal/recipe/provision.py b/internal/recipe/provision.py
index 8a8227163..53f9047ec 100644
--- a/internal/recipe/provision.py
+++ b/internal/recipe/provision.py
@@ -33,7 +33,7 @@ catalogs = {
         **{
             "type": "rest",
             "uri": "http://rest:8181";,
-            "s3.endpoint": "http://minio:9000";,
+            "s3.endpoint": "http://object-store:9000";,
             "s3.access-key-id": "admin",
             "s3.secret-access-key": "password",
         },
diff --git a/internal/recipe/spark-defaults.conf 
b/internal/recipe/spark-defaults.conf
index 5051bd5be..b5507408a 100644
--- a/internal/recipe/spark-defaults.conf
+++ b/internal/recipe/spark-defaults.conf
@@ -18,7 +18,7 @@ spark.sql.catalog.spark_catalog                      
org.apache.iceberg.spark.Sp
 spark.sql.catalog.spark_catalog.type                 rest
 spark.sql.catalog.spark_catalog.uri                  http://rest:8181
 spark.sql.catalog.spark_catalog.io-impl              
org.apache.iceberg.aws.s3.S3FileIO
-spark.sql.catalog.spark_catalog.s3.endpoint          http://minio:9000
+spark.sql.catalog.spark_catalog.s3.endpoint          http://object-store:9000
 spark.sql.catalog.spark_catalog.s3.path-style-access true
 spark.sql.catalog.spark_catalog.warehouse            s3://warehouse
 spark.sql.defaultCatalog                             spark_catalog
diff --git a/io/gocloud/s3/s3_integration_test.go 
b/io/gocloud/s3/s3_integration_test.go
index 3bae04c11..0f02f6c26 100644
--- a/io/gocloud/s3/s3_integration_test.go
+++ b/io/gocloud/s3/s3_integration_test.go
@@ -34,7 +34,7 @@ import (
        "github.com/uptrace/bun/driver/sqliteshim"
 )
 
-func TestMinioWarehouse(t *testing.T) {
+func TestS3Warehouse(t *testing.T) {
        _, err := recipe.Start(t)
        require.NoError(t, err)
 
@@ -66,7 +66,7 @@ func TestMinioWarehouse(t *testing.T) {
        require.NotNil(t, tbl)
 }
 
-func TestMinioWarehouseNoLocation(t *testing.T) {
+func TestS3WarehouseNoLocation(t *testing.T) {
        _, err := recipe.Start(t)
        require.NoError(t, err)
 
diff --git a/website/src/cli.md b/website/src/cli.md
index f799e7705..440b370d0 100644
--- a/website/src/cli.md
+++ b/website/src/cli.md
@@ -104,7 +104,7 @@ All commands accept `--output text` (default, 
human-readable) or `--output json`
 
 Note: only the identity transform is supported for `--partition-spec` at this 
moment.
 ```shell
-# Create a simple table with REST catalog and Minio
+# Create a simple table with REST catalog and object store
 ./iceberg create table default.table-1 \
         --properties write.format.default=parquet \
         --partition-spec foo \

Reply via email to