mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-09-21 18:17:05 +00:00
test: use RustFS for S3 service fixtures
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
docker stop minio-test
|
||||
docker rm minio-test
|
||||
docker rm -v minio-test
|
||||
|
||||
Reference in New Issue
Block a user