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

xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal.git


The following commit(s) were added to refs/heads/main by this push:
     new bd4b26bc0 feat(services/swift): add TempURL presigned URL support 
(#7214)
bd4b26bc0 is described below

commit bd4b26bc0e367a92333ff1a9728b4d1e0fb1bf1c
Author: Ben Roeder <[email protected]>
AuthorDate: Sun Feb 22 21:54:35 2026 -0800

    feat(services/swift): add TempURL presigned URL support (#7214)
    
    Add HMAC-signed TempURL support for presign operations (stat, read,
    write). Uses the prefixed base64 signature format (e.g. sha256:<base64>)
    which works universally across Swift deployments.
    
    - Supports SHA1, SHA256 (default), and SHA512 algorithms via
      temp_url_hash_algorithm config
    - Requires temp_url_key matching X-Account-Meta-Temp-URL-Key or
      X-Container-Meta-Temp-URL-Key on the Swift account/container
    - Configure TempURL key on SAIO for CI presign test coverage
    - Add multi-arch Dockerfile for native arm64 local testing
---
 .github/services/swift/swift/action.yml       |   3 +
 core/Cargo.lock                               |   5 +
 core/services/swift/Cargo.toml                |   5 +
 core/services/swift/src/backend.rs            |  64 +++++++++
 core/services/swift/src/config.rs             |  12 ++
 core/services/swift/src/core.rs               | 194 ++++++++++++++++++++++++++
 fixtures/swift/Dockerfile                     |  87 ++++++++++++
 fixtures/swift/docker-compose-swift-local.yml |  40 ++++++
 8 files changed, 410 insertions(+)

diff --git a/.github/services/swift/swift/action.yml 
b/.github/services/swift/swift/action.yml
index 7dc7e4ff1..3eff4fd32 100644
--- a/.github/services/swift/swift/action.yml
+++ b/.github/services/swift/swift/action.yml
@@ -34,6 +34,8 @@ runs:
         token=$(echo "$response" | grep X-Auth-Token | head -n1 | awk '{print 
$2}' | tr -d '[:space:]')
         endpoint=$(echo "$response" | grep X-Storage-Url | head -n1 | awk 
'{print $2}' | tr -d '[:space:]')
         curl --location --request PUT "${endpoint}/testing" --header 
"X-Auth-Token: $token"
+        # Configure TempURL key on the account for presigned URL tests
+        curl -s -X POST -H "X-Auth-Token: $token" -H 
"X-Account-Meta-Temp-URL-Key: opendal-swift-test-key" "${endpoint}"
         echo "OPENDAL_SWIFT_TOKEN=${token}" >> $GITHUB_ENV
         echo "OPENDAL_SWIFT_ENDPOINT=${endpoint}" >> $GITHUB_ENV
 
@@ -42,5 +44,6 @@ runs:
       run: |
         cat << EOF >> $GITHUB_ENV
         OPENDAL_SWIFT_CONTAINER=testing
+        OPENDAL_SWIFT_TEMP_URL_KEY=opendal-swift-test-key
         OPENDAL_SWIFT_ROOT=/
         EOF
diff --git a/core/Cargo.lock b/core/Cargo.lock
index 2c47306c7..eda8e61f5 100644
--- a/core/Cargo.lock
+++ b/core/Cargo.lock
@@ -7043,13 +7043,18 @@ dependencies = [
 name = "opendal-service-swift"
 version = "0.55.0"
 dependencies = [
+ "base64 0.22.1",
  "bytes",
+ "hmac",
  "http 1.4.0",
  "log",
  "opendal-core",
+ "percent-encoding",
  "quick-xml",
  "serde",
  "serde_json",
+ "sha1",
+ "sha2",
  "tokio",
  "uuid",
 ]
diff --git a/core/services/swift/Cargo.toml b/core/services/swift/Cargo.toml
index 0d5b9b02c..fab50090c 100644
--- a/core/services/swift/Cargo.toml
+++ b/core/services/swift/Cargo.toml
@@ -31,13 +31,18 @@ version = { workspace = true }
 all-features = true
 
 [dependencies]
+base64 = { workspace = true }
 bytes = { workspace = true }
+hmac = "0.12.1"
 http = { workspace = true }
 log = { workspace = true }
 opendal-core = { path = "../../core", version = "0.55.0", default-features = 
false }
+percent-encoding = "2"
 quick-xml = { workspace = true, features = ["serialize", "overlapped-lists"] }
 serde = { workspace = true, features = ["derive"] }
 serde_json = { workspace = true }
+sha1 = "0.10.6"
+sha2 = { workspace = true }
 uuid = { workspace = true, features = ["v4"] }
 
 [dev-dependencies]
diff --git a/core/services/swift/src/backend.rs 
b/core/services/swift/src/backend.rs
index 1a6ad309b..594e65f46 100644
--- a/core/services/swift/src/backend.rs
+++ b/core/services/swift/src/backend.rs
@@ -95,6 +95,30 @@ impl SwiftBuilder {
         }
         self
     }
+
+    /// Set the TempURL key for generating presigned URLs.
+    ///
+    /// This should match the `X-Account-Meta-Temp-URL-Key` or
+    /// `X-Container-Meta-Temp-URL-Key` value configured on the Swift
+    /// account or container.
+    pub fn temp_url_key(mut self, key: &str) -> Self {
+        if !key.is_empty() {
+            self.config.temp_url_key = Some(key.to_string());
+        }
+        self
+    }
+
+    /// Set the hash algorithm for TempURL signing.
+    ///
+    /// Supported values: `sha1`, `sha256`, `sha512`. Defaults to `sha256`.
+    /// The cluster must have the chosen algorithm in its
+    /// `tempurl.allowed_digests` (check `GET /info`).
+    pub fn temp_url_hash_algorithm(mut self, algo: &str) -> Self {
+        if !algo.is_empty() {
+            self.config.temp_url_hash_algorithm = Some(algo.to_string());
+        }
+        self
+    }
 }
 
 impl Builder for SwiftBuilder {
@@ -135,6 +159,12 @@ impl Builder for SwiftBuilder {
         };
 
         let token = self.config.token.unwrap_or_default();
+        let temp_url_key = self.config.temp_url_key.unwrap_or_default();
+        let has_temp_url_key = !temp_url_key.is_empty();
+        let temp_url_hash_algorithm = match 
&self.config.temp_url_hash_algorithm {
+            Some(algo) => TempUrlHashAlgorithm::from_str_opt(algo)?,
+            None => TempUrlHashAlgorithm::Sha256,
+        };
 
         Ok(SwiftBackend {
             core: Arc::new(SwiftCore {
@@ -178,6 +208,11 @@ impl Builder for SwiftBuilder {
                             list: true,
                             list_with_recursive: true,
 
+                            presign: has_temp_url_key,
+                            presign_stat: has_temp_url_key,
+                            presign_read: has_temp_url_key,
+                            presign_write: has_temp_url_key,
+
                             shared: true,
 
                             ..Default::default()
@@ -188,6 +223,8 @@ impl Builder for SwiftBuilder {
                 endpoint,
                 container,
                 token,
+                temp_url_key,
+                temp_url_hash_algorithm,
             }),
         })
     }
@@ -271,6 +308,33 @@ impl Access for SwiftBackend {
         Ok((RpList::default(), oio::PageLister::new(l)))
     }
 
+    async fn presign(&self, path: &str, args: OpPresign) -> Result<RpPresign> {
+        let (expire, op) = args.into_parts();
+
+        let method = match &op {
+            PresignOperation::Stat(_) => http::Method::HEAD,
+            PresignOperation::Read(_) => http::Method::GET,
+            PresignOperation::Write(_) => http::Method::PUT,
+            _ => {
+                return Err(Error::new(
+                    ErrorKind::Unsupported,
+                    "presign operation is not supported",
+                ));
+            }
+        };
+
+        let url = self.core.swift_temp_url(&method, path, expire)?;
+        let uri: http::Uri = url.parse().map_err(|e| {
+            Error::new(ErrorKind::Unexpected, "failed to parse presigned 
URL").set_source(e)
+        })?;
+
+        Ok(RpPresign::new(PresignedRequest::new(
+            method,
+            uri,
+            http::HeaderMap::new(),
+        )))
+    }
+
     async fn copy(&self, from: &str, to: &str, _args: OpCopy) -> 
Result<RpCopy> {
         // cannot copy objects larger than 5 GB.
         // Reference: 
https://docs.openstack.org/api-ref/object-store/#copy-object
diff --git a/core/services/swift/src/config.rs 
b/core/services/swift/src/config.rs
index 63e928aac..efaf46653 100644
--- a/core/services/swift/src/config.rs
+++ b/core/services/swift/src/config.rs
@@ -36,6 +36,18 @@ pub struct SwiftConfig {
     pub root: Option<String>,
     /// The token for Swift.
     pub token: Option<String>,
+    /// The TempURL key for generating presigned URLs.
+    ///
+    /// This corresponds to the `X-Account-Meta-Temp-URL-Key` or
+    /// `X-Container-Meta-Temp-URL-Key` header value configured on the
+    /// Swift account or container.
+    pub temp_url_key: Option<String>,
+    /// The hash algorithm for TempURL signing.
+    ///
+    /// Supported values: `sha1`, `sha256`, `sha512`. Defaults to `sha256`.
+    /// The cluster must have the chosen algorithm in its
+    /// `tempurl.allowed_digests` (check `GET /info`).
+    pub temp_url_hash_algorithm: Option<String>,
 }
 
 impl Debug for SwiftConfig {
diff --git a/core/services/swift/src/core.rs b/core/services/swift/src/core.rs
index ccd5d34c5..310e248fc 100644
--- a/core/services/swift/src/core.rs
+++ b/core/services/swift/src/core.rs
@@ -17,7 +17,12 @@
 
 use std::fmt::Debug;
 use std::sync::Arc;
+use std::time::Duration;
+use std::time::SystemTime;
 
+use hmac::Hmac;
+use hmac::Mac;
+use http::Method;
 use http::Request;
 use http::Response;
 use http::header;
@@ -27,16 +32,85 @@ use http::header::IF_NONE_MATCH;
 use http::header::IF_UNMODIFIED_SINCE;
 use serde::Deserialize;
 use serde::Serialize;
+use sha1::Sha1;
+use sha2::Sha256;
+use sha2::Sha512;
 
 use opendal_core::raw::*;
 use opendal_core::*;
 
+/// The HMAC hash algorithm used for TempURL signing.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum TempUrlHashAlgorithm {
+    Sha1,
+    Sha256,
+    Sha512,
+}
+
+impl TempUrlHashAlgorithm {
+    pub fn from_str_opt(s: &str) -> Result<Self> {
+        match s.to_lowercase().as_str() {
+            "sha1" => Ok(Self::Sha1),
+            "sha256" => Ok(Self::Sha256),
+            "sha512" => Ok(Self::Sha512),
+            _ => Err(Error::new(
+                ErrorKind::ConfigInvalid,
+                format!(
+                    "unsupported temp_url_hash_algorithm: {s}. Expected: sha1, 
sha256, or sha512"
+                ),
+            )),
+        }
+    }
+
+    /// Compute HMAC and return the signature in `algo:base64` format.
+    ///
+    /// Swift's TempURL middleware supports two signature formats
+    /// (see `extract_digest_and_algorithm` in swift/common/digest.py):
+    /// - Plain hex with length-based algorithm detection
+    ///   (40 chars = SHA1, 64 = SHA256, 128 = SHA512)
+    /// - Prefixed base64: `sha1:<base64>`, `sha256:<base64>`, 
`sha512:<base64>`
+    ///
+    /// We use the prefixed base64 format as it explicitly declares the
+    /// algorithm and avoids ambiguity.
+    ///
+    /// References:
+    /// - 
<https://docs.openstack.org/swift/latest/api/temporary_url_middleware.html>
+    /// - 
<https://github.com/openstack/swift/blob/master/swift/common/digest.py>
+    fn hmac_sign(&self, key: &[u8], data: &[u8]) -> String {
+        use base64::Engine;
+        let engine = base64::engine::general_purpose::STANDARD;
+
+        match self {
+            Self::Sha1 => {
+                let mut mac =
+                    Hmac::<Sha1>::new_from_slice(key).expect("HMAC can take 
key of any size");
+                mac.update(data);
+                format!("sha1:{}", engine.encode(mac.finalize().into_bytes()))
+            }
+            Self::Sha256 => {
+                let mut mac =
+                    Hmac::<Sha256>::new_from_slice(key).expect("HMAC can take 
key of any size");
+                mac.update(data);
+                format!("sha256:{}", 
engine.encode(mac.finalize().into_bytes()))
+            }
+            Self::Sha512 => {
+                let mut mac =
+                    Hmac::<Sha512>::new_from_slice(key).expect("HMAC can take 
key of any size");
+                mac.update(data);
+                format!("sha512:{}", 
engine.encode(mac.finalize().into_bytes()))
+            }
+        }
+    }
+}
+
 pub struct SwiftCore {
     pub info: Arc<AccessorInfo>,
     pub root: String,
     pub endpoint: String,
     pub container: String,
     pub token: String,
+    pub temp_url_key: String,
+    pub temp_url_hash_algorithm: TempUrlHashAlgorithm,
 }
 
 impl Debug for SwiftCore {
@@ -461,6 +535,70 @@ impl SwiftCore {
 
         Ok(())
     }
+
+    /// Generate a TempURL (presigned URL) for the given object.
+    ///
+    /// Uses the configured hash algorithm (default SHA256) with `algo:base64`
+    /// signature format for universal compatibility across Swift deployments.
+    ///
+    /// Reference: 
<https://docs.openstack.org/swift/latest/api/temporary_url_middleware.html>
+    pub fn swift_temp_url(&self, method: &Method, path: &str, expire: 
Duration) -> Result<String> {
+        if self.temp_url_key.is_empty() {
+            return Err(Error::new(
+                ErrorKind::ConfigInvalid,
+                "temp_url_key is required for presign",
+            ));
+        }
+
+        let abs = build_abs_path(&self.root, path);
+
+        // Extract the path portion from the endpoint URL for signing.
+        // The endpoint is like "https://host:port/v1/AUTH_account";.
+        // The signing path must be: /v1/AUTH_account/container/object
+        // Find the path by looking for the third '/' (after "https://host";).
+        let account_path = self
+            .endpoint
+            .find("://")
+            .and_then(|scheme_end| {
+                self.endpoint[scheme_end + 3..]
+                    .find('/')
+                    .map(|i| scheme_end + 3 + i)
+            })
+            .map(|path_start| 
self.endpoint[path_start..].trim_end_matches('/'))
+            .unwrap_or("");
+        let signing_path = format!(
+            "{}/{}/{}",
+            account_path,
+            &self.container,
+            abs.trim_start_matches('/')
+        );
+
+        let expires = SystemTime::now()
+            .duration_since(SystemTime::UNIX_EPOCH)
+            .expect("system time before epoch")
+            .as_secs()
+            + expire.as_secs();
+
+        let sig_body = format!("{}\n{}\n{}", method.as_str(), expires, 
signing_path);
+
+        let signature = self
+            .temp_url_hash_algorithm
+            .hmac_sign(self.temp_url_key.as_bytes(), sig_body.as_bytes());
+
+        // The signature is in `algo:base64` format which contains characters
+        // that need percent-encoding in query parameters (+, /, =, :).
+        let encoded_sig =
+            percent_encoding::utf8_percent_encode(&signature, 
percent_encoding::NON_ALPHANUMERIC);
+
+        Ok(format!(
+            "{}/{}/{}?temp_url_sig={}&temp_url_expires={}",
+            &self.endpoint,
+            &self.container,
+            percent_encode_path(&abs),
+            &encoded_sig,
+            expires
+        ))
+    }
 }
 
 #[derive(Debug, Eq, PartialEq, Deserialize)]
@@ -620,4 +758,60 @@ mod tests {
 
         Ok(())
     }
+
+    #[test]
+    fn temp_url_sha1_signature_format() {
+        let algo = TempUrlHashAlgorithm::Sha1;
+        let sig = algo.hmac_sign(b"secret", 
b"GET\n1234567890\n/v1/AUTH_test/c/obj");
+        assert!(
+            sig.starts_with("sha1:"),
+            "SHA1 signature must start with 'sha1:'"
+        );
+        // SHA1 = 20 bytes = 28 base64 chars (with padding)
+        let b64_part = &sig["sha1:".len()..];
+        assert_eq!(b64_part.len(), 28, "SHA1 base64 must be 28 chars");
+    }
+
+    #[test]
+    fn temp_url_sha256_signature_format() {
+        let algo = TempUrlHashAlgorithm::Sha256;
+        let sig = algo.hmac_sign(b"secret", 
b"GET\n1234567890\n/v1/AUTH_test/c/obj");
+        assert!(
+            sig.starts_with("sha256:"),
+            "SHA256 signature must start with 'sha256:'"
+        );
+        // SHA256 = 32 bytes = 44 base64 chars (with padding)
+        let b64_part = &sig["sha256:".len()..];
+        assert_eq!(b64_part.len(), 44, "SHA256 base64 must be 44 chars");
+    }
+
+    #[test]
+    fn temp_url_sha512_signature_format() {
+        let algo = TempUrlHashAlgorithm::Sha512;
+        let sig = algo.hmac_sign(b"secret", 
b"GET\n1234567890\n/v1/AUTH_test/c/obj");
+        assert!(
+            sig.starts_with("sha512:"),
+            "SHA512 signature must start with 'sha512:'"
+        );
+        // SHA512 = 64 bytes = 88 base64 chars (with padding)
+        let b64_part = &sig["sha512:".len()..];
+        assert_eq!(b64_part.len(), 88, "SHA512 base64 must be 88 chars");
+    }
+
+    #[test]
+    fn temp_url_hash_algorithm_from_str() {
+        assert_eq!(
+            TempUrlHashAlgorithm::from_str_opt("sha1").unwrap(),
+            TempUrlHashAlgorithm::Sha1
+        );
+        assert_eq!(
+            TempUrlHashAlgorithm::from_str_opt("SHA256").unwrap(),
+            TempUrlHashAlgorithm::Sha256
+        );
+        assert_eq!(
+            TempUrlHashAlgorithm::from_str_opt("Sha512").unwrap(),
+            TempUrlHashAlgorithm::Sha512
+        );
+        assert!(TempUrlHashAlgorithm::from_str_opt("md5").is_err());
+    }
 }
diff --git a/fixtures/swift/Dockerfile b/fixtures/swift/Dockerfile
new file mode 100644
index 000000000..981bd2528
--- /dev/null
+++ b/fixtures/swift/Dockerfile
@@ -0,0 +1,87 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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.
+
+# Multi-arch Swift All-In-One (SAIO) image
+#
+# Based on upstream: 
https://opendev.org/openstack/swift/src/branch/master/Dockerfile
+# Modified to support both amd64 and arm64 (Apple Silicon).
+#
+# Build for current platform:
+#   podman build -t opendal/swift-saio .
+#
+# Build multi-arch:
+#   docker buildx build --platform linux/amd64,linux/arm64 -t 
opendal/swift-saio .
+
+FROM alpine:3.16.2
+
+# TARGETARCH is automatically set by buildx/podman (amd64 or arm64).
+ARG TARGETARCH
+
+ENV S6_LOGGING=1
+ENV S6_VERSION=1.21.4.0
+ENV SOCKLOG_VERSION=3.0.1-1
+ENV BUILD_DIR="/tmp"
+ENV ENV="/etc/profile"
+
+# Map Docker arch names to s6-overlay arch names:
+#   amd64  -> amd64
+#   arm64  -> aarch64
+RUN if [ "$TARGETARCH" = "arm64" ]; then \
+        echo "aarch64" > /tmp/s6_arch; \
+    else \
+        echo "$TARGETARCH" > /tmp/s6_arch; \
+    fi
+
+# Clone Swift source
+RUN apk add --no-cache git && \
+    git clone --depth 1 https://opendev.org/openstack/swift.git /opt/swift && \
+    apk del git
+
+# Download s6-overlay and socklog-overlay for the target architecture
+RUN S6_ARCH=$(cat /tmp/s6_arch) && \
+    wget -q -O /tmp/s6-overlay.tar.gz \
+        
"https://github.com/just-containers/s6-overlay/releases/download/v${S6_VERSION}/s6-overlay-${S6_ARCH}.tar.gz";
 && \
+    wget -q -O /tmp/s6-overlay.tar.gz.sig \
+        
"https://github.com/just-containers/s6-overlay/releases/download/v${S6_VERSION}/s6-overlay-${S6_ARCH}.tar.gz.sig";
 && \
+    wget -q -O /tmp/socklog-overlay.tar.gz \
+        
"https://github.com/just-containers/socklog-overlay/releases/download/v${SOCKLOG_VERSION}/socklog-overlay-${S6_ARCH}.tar.gz";
+
+RUN mkdir /etc/swift && \
+    echo "================   starting swift_needs  ===================" && \
+    /opt/swift/docker/install_scripts/00_swift_needs.sh && \
+    echo "================   starting apk_install_prereqs  
===================" && \
+    /opt/swift/docker/install_scripts/10_apk_install_prereqs.sh && \
+    echo "================   starting apk_install_py3  ===================" && 
\
+    /opt/swift/docker/install_scripts/21_apk_install_py3.sh && \
+    echo "================   starting swift_install  ===================" && \
+    /opt/swift/docker/install_scripts/50_swift_install.sh && \
+    echo "================   installing s6-overlay  ===================" && \
+    gpg --import /opt/swift/docker/s6-gpg-pub-key && \
+    gpg --verify /tmp/s6-overlay.tar.gz.sig /tmp/s6-overlay.tar.gz && \
+    gunzip -c /tmp/s6-overlay.tar.gz | tar -xf - -C / && \
+    gunzip -c /tmp/socklog-overlay.tar.gz | tar -xf - -C / && \
+    rm -rf /tmp/s6-overlay* /tmp/socklog-overlay* /tmp/s6_arch && \
+    echo "================   starting pip_uninstall_dev  ===================" 
&& \
+    /opt/swift/docker/install_scripts/60_pip_uninstall_dev.sh && \
+    echo "================   starting apk_uninstall_dev  ===================" 
&& \
+    /opt/swift/docker/install_scripts/99_apk_uninstall_dev.sh
+
+# The upstream Dockerfile uses "COPY docker/rootfs /" since it builds
+# from the Swift source tree. We cloned into /opt/swift, so copy from there.
+RUN cp -a /opt/swift/docker/rootfs/* /
+
+ENTRYPOINT ["/init"]
diff --git a/fixtures/swift/docker-compose-swift-local.yml 
b/fixtures/swift/docker-compose-swift-local.yml
new file mode 100644
index 000000000..78385ddb5
--- /dev/null
+++ b/fixtures/swift/docker-compose-swift-local.yml
@@ -0,0 +1,40 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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.
+
+# Locally-built Swift SAIO for native arm64/aarch64 support.
+#
+# The upstream openstackswift/saio:py3 image is amd64-only.
+# This compose file builds from the Dockerfile which supports
+# both amd64 and arm64 natively.
+#
+# Usage:
+#   docker compose -f docker-compose-swift-local.yml up -d --build --wait
+#   podman compose -f docker-compose-swift-local.yml up -d --build --wait
+
+services:
+  swift:
+    build:
+      context: .
+      dockerfile: Dockerfile
+    image: opendal-swift-saio:local
+    ports:
+      - 8080:8080
+    healthcheck:
+      test: ["CMD", "curl", "-i", "-H", "X-Storage-User: test:tester", "-H", 
"X-Storage-Pass: testing", "http://localhost:8080/";]
+      interval: 3s
+      timeout: 20s
+      retries: 10

Reply via email to