test: use RustFS for S3 service fixtures

This commit is contained in:
vorotamoroz
2026-09-15 13:32:55 +00:00
parent 6f0892a825
commit d082b409c0
13 changed files with 157 additions and 133 deletions
+27 -10
View File
@@ -307,10 +307,19 @@ cli_test_wait_for_minio_bucket() {
local delay_sec=2
local i
for ((i = 1; i <= retries; i++)); do
if docker run --rm --network host --entrypoint=/bin/sh minio/mc -c "mc alias set myminio $minio_endpoint $minio_access_key $minio_secret_key >/dev/null 2>&1 && mc ls myminio/$minio_bucket >/dev/null 2>&1"; then
if docker run --rm --network host --entrypoint=/bin/sh \
rustfs/rc:v0.1.35@sha256:adb45b56539006120f1d790bcc17ee5f9b4d93c1d7e71ed0a24f10267f9d6914 \
-c 'set -e
rc alias set myminio "$1" "$2" "$3" >/dev/null 2>&1
rc ls "myminio/$4" >/dev/null 2>&1
' sh "$minio_endpoint" "$minio_access_key" "$minio_secret_key" "$minio_bucket"; then
return 0
fi
bucketName="$minio_bucket" bash "$CLI_DIR/util/minio-init.sh" >/dev/null 2>&1 || true
minioEndpoint="$minio_endpoint" \
accessKey="$minio_access_key" \
secretKey="$minio_secret_key" \
bucketName="$minio_bucket" \
bash "$CLI_DIR/util/minio-init.sh" >/dev/null 2>&1 || true
sleep "$delay_sec"
done
return 1
@@ -323,26 +332,34 @@ cli_test_start_minio() {
local minio_bucket="$4"
local minio_init_ok=0
echo "[INFO] stopping leftover MinIO container if present"
echo "[INFO] stopping leftover RustFS container if present"
cli_test_stop_minio
echo "[INFO] starting MinIO test container"
bucketName="$minio_bucket" bash "$CLI_DIR/util/minio-start.sh"
echo "[INFO] starting RustFS test container"
minioEndpoint="$minio_endpoint" \
accessKey="$minio_access_key" \
secretKey="$minio_secret_key" \
bucketName="$minio_bucket" \
bash "$CLI_DIR/util/minio-start.sh"
echo "[INFO] initialising MinIO test bucket: $minio_bucket"
echo "[INFO] initialising RustFS test bucket: $minio_bucket"
for _ in 1 2 3 4 5; do
if bucketName="$minio_bucket" bash "$CLI_DIR/util/minio-init.sh"; then
if minioEndpoint="$minio_endpoint" \
accessKey="$minio_access_key" \
secretKey="$minio_secret_key" \
bucketName="$minio_bucket" \
bash "$CLI_DIR/util/minio-init.sh"; then
minio_init_ok=1
break
fi
sleep 2
done
if [[ "$minio_init_ok" != "1" ]]; then
echo "[FAIL] could not initialise MinIO bucket after retries: $minio_bucket" >&2
echo "[FAIL] could not initialise RustFS bucket after retries: $minio_bucket" >&2
exit 1
fi
if ! cli_test_wait_for_minio_bucket "$minio_endpoint" "$minio_access_key" "$minio_secret_key" "$minio_bucket"; then
echo "[FAIL] MinIO bucket not ready: $minio_bucket" >&2
echo "[FAIL] RustFS bucket not ready: $minio_bucket" >&2
exit 1
fi
}
@@ -359,4 +376,4 @@ display_test_info(){
if [[ "${LIVESYNC_TEST_DOCKER:-0}" == "1" ]]; then
# shellcheck source=/dev/null
source "$(dirname "${BASH_SOURCE[0]}")/test-helpers-docker.sh"
fi
fi
+37 -23
View File
@@ -190,7 +190,7 @@ async function dockerOrFail(...args: string[]): Promise<string> {
async function stopAndRemoveContainer(container: string): Promise<void> {
await docker("stop", container).catch(() => {});
await docker("rm", container).catch(() => {});
await docker("rm", "-v", container).catch(() => {});
}
async function cleanupTrackedContainers(reason: string): Promise<void> {
@@ -327,8 +327,22 @@ const COUCHDB_CONTAINER = "couchdb-test";
const COUCHDB_IMAGE = "couchdb:3.5.0";
const MINIO_CONTAINER = "minio-test";
const MINIO_IMAGE = "minio/minio";
const MINIO_MC_IMAGE = "minio/mc";
// RustFS provides the S3 backend for the existing MINIO test mode.
const S3_IMAGE = "rustfs/rustfs:1.0.0-rc.6@sha256:97171b3d72cd47dc81000f92ea84de25608bfc35a94c965501afaeb5d99f6035";
const S3_CLIENT_IMAGE = "rustfs/rc:v0.1.35@sha256:adb45b56539006120f1d790bcc17ee5f9b4d93c1d7e71ed0a24f10267f9d6914";
const S3_BUCKET_CORS = `<CORSConfiguration>
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<AllowedMethod>PUT</AllowedMethod>
<AllowedMethod>POST</AllowedMethod>
<AllowedMethod>DELETE</AllowedMethod>
<AllowedMethod>HEAD</AllowedMethod>
<AllowedHeader>*</AllowedHeader>
<AllowedHeader>authorization</AllowedHeader>
<ExposeHeader>ETag</ExposeHeader>
</CORSRule>
</CORSConfiguration>`;
export async function stopCouchdb(): Promise<void> {
await stopAndRemoveContainer(COUCHDB_CONTAINER);
@@ -454,7 +468,7 @@ export async function updateCouchdbDoc(
}
// ---------------------------------------------------------------------------
// MinIO
// S3 (RustFS)
// ---------------------------------------------------------------------------
function shQuote(value: string): string {
@@ -473,9 +487,10 @@ async function initMinioBucket(
bucket: string
): Promise<boolean> {
const cmd =
`mc alias set myminio ${shQuote(minioEndpoint)} ${shQuote(accessKey)} ${shQuote(secretKey)} >/dev/null 2>&1 && ` +
`mc mb --ignore-existing myminio/${shQuote(bucket)} >/dev/null 2>&1`;
const r = await docker("run", "--rm", "--network", "host", "--entrypoint", "/bin/sh", MINIO_MC_IMAGE, "-c", cmd);
`rc alias set myminio ${shQuote(minioEndpoint)} ${shQuote(accessKey)} ${shQuote(secretKey)} >/dev/null 2>&1 && ` +
`rc mb --ignore-existing myminio/${shQuote(bucket)} >/dev/null 2>&1 && ` +
`printf %s ${shQuote(S3_BUCKET_CORS)} | rc cors set myminio/${shQuote(bucket)} - >/dev/null 2>&1`;
const r = await docker("run", "--rm", "--network", "host", "--entrypoint", "/bin/sh", S3_CLIENT_IMAGE, "-c", cmd);
return r.code === 0;
}
@@ -487,8 +502,8 @@ async function waitForMinioBucket(
): Promise<void> {
for (let i = 0; i < 30; i++) {
const checkCmd =
`mc alias set myminio ${shQuote(minioEndpoint)} ${shQuote(accessKey)} ${shQuote(secretKey)} >/dev/null 2>&1 && ` +
`mc ls myminio/${shQuote(bucket)} >/dev/null 2>&1`;
`rc alias set myminio ${shQuote(minioEndpoint)} ${shQuote(accessKey)} ${shQuote(secretKey)} >/dev/null 2>&1 && ` +
`rc ls myminio/${shQuote(bucket)} >/dev/null 2>&1`;
const check = await docker(
"run",
"--rm",
@@ -498,7 +513,7 @@ async function waitForMinioBucket(
"host",
"--entrypoint",
"/bin/sh",
MINIO_MC_IMAGE,
S3_CLIENT_IMAGE,
"-c",
checkCmd
);
@@ -508,7 +523,7 @@ async function waitForMinioBucket(
await initMinioBucket(minioEndpoint, accessKey, secretKey, bucket);
await sleep(2000);
}
throw new Error(`MinIO bucket not ready: ${bucket}`);
throw new Error(`S3 bucket not ready: ${bucket}`);
}
export async function startMinio(
@@ -517,10 +532,10 @@ export async function startMinio(
secretKey: string,
bucket: string
): Promise<void> {
console.log("[INFO] stopping leftover MinIO container if present");
console.log("[INFO] stopping leftover S3 test container if present");
await stopMinio().catch(() => {});
console.log("[INFO] starting MinIO test container");
console.log("[INFO] starting RustFS test container");
await dockerOrFail(
"run",
"-d",
@@ -532,20 +547,19 @@ export async function startMinio(
"-p",
"9001:9001",
"-e",
`MINIO_ROOT_USER=${accessKey}`,
`RUSTFS_ACCESS_KEY=${accessKey}`,
"-e",
`MINIO_ROOT_PASSWORD=${secretKey}`,
`RUSTFS_SECRET_KEY=${secretKey}`,
"-e",
`MINIO_SERVER_URL=${minioEndpoint}`,
MINIO_IMAGE,
"server",
"/data",
"--console-address",
":9001"
"RUSTFS_CONSOLE_ENABLE=true",
"-e",
"RUSTFS_CORS_ALLOWED_ORIGINS=*",
S3_IMAGE,
"/data"
);
trackContainer(MINIO_CONTAINER);
console.log(`[INFO] initialising MinIO test bucket: ${bucket}`);
console.log(`[INFO] initialising S3 test bucket: ${bucket}`);
let initialised = false;
for (let i = 0; i < 5; i++) {
if (await initMinioBucket(minioEndpoint, accessKey, secretKey, bucket)) {
@@ -555,7 +569,7 @@ export async function startMinio(
await sleep(2000);
}
if (!initialised) {
throw new Error(`Could not initialise MinIO bucket after retries: ${bucket}`);
throw new Error(`Could not initialise S3 bucket after retries: ${bucket}`);
}
await waitForMinioBucket(minioEndpoint, accessKey, secretKey, bucket);
@@ -60,6 +60,32 @@ export async function runScenario(remoteType: RemoteType, encrypt: boolean): Pro
}
try {
if (remoteType === "MINIO") {
// The shared S3 fixture also serves the browser and real Obsidian tests.
const origin = "app://obsidian.md";
const requestedHeaders = ["authorization", "content-type", "x-amz-date", "x-amz-content-sha256"];
const preflight = await fetch(`${minioEndpoint}/${minioBucket}`, {
method: "OPTIONS",
headers: {
Origin: origin,
"Access-Control-Request-Method": "PUT",
"Access-Control-Request-Headers": requestedHeaders.join(","),
},
});
await preflight.body?.cancel();
assert(preflight.ok, "The S3 fixture must accept browser preflight requests");
const allowedOrigin = preflight.headers.get("access-control-allow-origin");
assert(allowedOrigin === "*" || allowedOrigin === origin, "The S3 fixture must allow the Obsidian origin");
const allowedHeaders = (preflight.headers.get("access-control-allow-headers") ?? "")
.toLowerCase()
.split(",")
.map((header) => header.trim());
assert(allowedHeaders.includes("authorization"), "S3 CORS must explicitly allow the Authorization header");
assert(
preflight.headers.get("access-control-allow-methods")?.split(/,\s*/).includes("PUT"),
"S3 CORS must allow browser uploads"
);
}
await initSettingsFile(settingsA);
await initSettingsFile(settingsB);
await applyRemoteSyncSettings(settingsA, {
+4 -3
View File
@@ -99,11 +99,12 @@ This file corresponds to settings helpers in `test-helpers.sh`.
### `helpers/docker.ts`
- Starts, stops, and initialises CouchDB directly from Deno.
- Starts, stops, and initialises CouchDB and RustFS directly from Deno.
- Configures CouchDB via `fetch + retry`.
- Initialises S3 buckets using the RustFS `rc` client, including CORS for signed browser requests.
- Starts and stops the P2P relay through the same Docker runner.
Both CouchDB and P2P relay flows are bash-independent.
These flows do not require Bash on the host. The S3 matrix tasks, environment variables, and container name retain their existing `minio` names for compatibility; RustFS provides the test backend. The RustFS server and `rc` client images are pinned by version and digest.
### `helpers/backgroundCli.ts`
@@ -328,7 +329,7 @@ The GitHub Actions workflow `.github/workflows/cli-deno-tests.yml` runs automati
## Current limitations
- MinIO startup and matrix coverage are ported. Current limits are elsewhere, not setup URI generation.
- S3 startup and matrix coverage use RustFS. Current limits are elsewhere, not setup URI generation.
---
+21 -44
View File
@@ -1,47 +1,24 @@
#!/bin/bash
set -e
cat >/tmp/mybucket-rw.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetBucketLocation","s3:ListBucket"],
"Resource": ["arn:aws:s3:::$bucketName"]
},
{
"Effect": "Allow",
"Action": ["s3:GetObject","s3:PutObject","s3:DeleteObject"],
"Resource": ["arn:aws:s3:::$bucketName/*"]
}
]
}
EOF
# echo "<CORSConfiguration>
# <CORSRule>
# <AllowedOrigin>http://localhost:63315</AllowedOrigin>
# <AllowedOrigin>http://localhost:63316</AllowedOrigin>
# <AllowedOrigin>http://localhost</AllowedOrigin>
# <AllowedMethod>GET</AllowedMethod>
# <AllowedMethod>PUT</AllowedMethod>
# <AllowedMethod>POST</AllowedMethod>
# <AllowedMethod>DELETE</AllowedMethod>
# <AllowedMethod>HEAD</AllowedMethod>
# <AllowedHeader>*</AllowedHeader>
# </CORSRule>
# </CORSConfiguration>" > /tmp/cors.xml
# docker run --rm --network host -v /tmp/mybucket-rw.json:/tmp/mybucket-rw.json --entrypoint=/bin/sh minio/mc -c "
# mc alias set myminio $minioEndpoint $username $password
# mc mb --ignore-existing myminio/$bucketName
# mc admin policy create myminio my-custom-policy /tmp/mybucket-rw.json
# echo 'Creating service account for user $username with access key $accessKey'
# mc admin user svcacct add --access-key '$accessKey' --secret-key '$secretKey' myminio '$username'
# mc admin policy attach myminio my-custom-policy --user '$accessKey'
# echo 'Verifying policy and user creation:'
# mc admin user svcacct info myminio '$accessKey'
# "
docker run --rm --network host -v /tmp/mybucket-rw.json:/tmp/mybucket-rw.json --entrypoint=/bin/sh minio/mc -c "
mc alias set myminio $minioEndpoint $accessKey $secretKey
mc mb --ignore-existing myminio/$bucketName
"
docker run --rm --network host --entrypoint=/bin/sh \
rustfs/rc:v0.1.35@sha256:adb45b56539006120f1d790bcc17ee5f9b4d93c1d7e71ed0a24f10267f9d6914 \
-c 'set -e
rc alias set myminio "$1" "$2" "$3"
rc mb --ignore-existing "myminio/$4"
rc cors set "myminio/$4" - <<CORS
<CORSConfiguration>
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<AllowedMethod>PUT</AllowedMethod>
<AllowedMethod>POST</AllowedMethod>
<AllowedMethod>DELETE</AllowedMethod>
<AllowedMethod>HEAD</AllowedMethod>
<AllowedHeader>*</AllowedHeader>
<AllowedHeader>authorization</AllowedHeader>
<ExposeHeader>ETag</ExposeHeader>
</CORSRule>
</CORSConfiguration>
CORS
' sh "$minioEndpoint" "$accessKey" "$secretKey" "$bucketName"
+7 -1
View File
@@ -1,2 +1,8 @@
#!/bin/bash
docker run -d --name minio-test -p 9000:9000 -p 9001:9001 -e MINIO_ROOT_USER=$accessKey -e MINIO_ROOT_PASSWORD=$secretKey -e MINIO_SERVER_URL=$minioEndpoint minio/minio server /data --console-address ':9001'
docker run -d --name minio-test \
-p 9000:9000 -p 9001:9001 \
-e "RUSTFS_ACCESS_KEY=$accessKey" \
-e "RUSTFS_SECRET_KEY=$secretKey" \
-e "RUSTFS_CONSOLE_ENABLE=true" \
-e 'RUSTFS_CORS_ALLOWED_ORIGINS=*' \
rustfs/rustfs:1.0.0-rc.6@sha256:97171b3d72cd47dc81000f92ea84de25608bfc35a94c965501afaeb5d99f6035 /data
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/bash
docker stop minio-test
docker rm minio-test
docker rm -v minio-test