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

juergbi pushed a commit to branch jbilleter/http-cache
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit c1f1af0b9190259ffe0befb545c2c3d6d23c5fc9
Author: Jürg Billeter <[email protected]>
AuthorDate: Fri Aug 7 15:26:02 2026 +0200

    _remotespec.py: Add `protocol` option for HTTP REST support
---
 src/buildstream/_assetcache.py |  3 +++
 src/buildstream/_remotespec.py | 29 +++++++++++++++++++++++++++--
 2 files changed, 30 insertions(+), 2 deletions(-)

diff --git a/src/buildstream/_assetcache.py b/src/buildstream/_assetcache.py
index 9406b9cf9..f2f8aa1e8 100644
--- a/src/buildstream/_assetcache.py
+++ b/src/buildstream/_assetcache.py
@@ -56,6 +56,9 @@ class AssetRemote(BaseRemote):
     #     RemoteError: If the upstream has a problem
     #
     def _check(self):
+        if self.spec.protocol != "grpc":
+            raise RemoteError("Index servers are supported only with the 
'grpc' protocol")
+
         request = remote_asset_pb2.FetchBlobRequest()
         if self.instance_name:
             request.instance_name = self.instance_name
diff --git a/src/buildstream/_remotespec.py b/src/buildstream/_remotespec.py
index f0ffd4d3e..375968ff6 100644
--- a/src/buildstream/_remotespec.py
+++ b/src/buildstream/_remotespec.py
@@ -65,6 +65,7 @@ class RemoteSpec:
         url: str,
         *,
         push: bool = False,
+        protocol: Optional[str] = None,
         server_cert: Optional[str] = None,
         client_key: Optional[str] = None,
         client_cert: Optional[str] = None,
@@ -75,6 +76,8 @@ class RemoteSpec:
         spec_node: Optional[MappingNode] = None,
     ) -> None:
 
+        provenance = "{}: ".format(spec_node.get_provenance()) if spec_node 
else ""
+
         #
         # Public members
         #
@@ -88,6 +91,19 @@ class RemoteSpec:
         # The url of the remote, this may contain a port number
         self.url: str = url
 
+        # The protocol to use
+        self.protocol: str = protocol or "grpc"
+        if self.protocol not in ["grpc", "http"]:
+            raise RemoteError(f"{provenance}Value for 'protocol' must be 
'grpc' or 'http', got: {self.protocol}")
+
+        if self.protocol == "http":
+            if self.remote_type != RemoteType.STORAGE:
+                raise RemoteError(
+                    f"{provenance}Value for remote 'type' must be 
'{RemoteType.STORAGE}' for HTTP remotes"
+                )
+            if self.push:
+                raise RemoteError(f"{provenance}Push is not supported for HTTP 
remotes")
+
         # The name of the grpc service to talk to at this remote url
         self.instance_name: Optional[str] = instance_name
 
@@ -127,6 +143,7 @@ class RemoteSpec:
                 self.remote_type,
                 self.push,
                 self.url,
+                self.protocol,
                 self.instance_name,
                 self.server_cert_file,
                 self.client_key_file,
@@ -141,7 +158,7 @@ class RemoteSpec:
         )
 
     def __str__(self) -> str:
-        string = self.url + "\n"
+        string = f"{self.url} ({self.protocol})\n"
         string += "push: {} type: {} instance: {}\n".format(self.push, 
self.remote_type, self.instance_name)
         if self._spec_node:
             provenance = str(self._spec_node.get_provenance())
@@ -210,6 +227,7 @@ class RemoteSpec:
     #
     def to_localcas_remote(self, remote):
         remote.url = self.url
+        remote.protocol = self.protocol
         if self.instance_name:
             remote.instance_name = self.instance_name
         if self.server_cert:
@@ -264,7 +282,7 @@ class RemoteSpec:
         push: bool = False
         remote_type: str = RemoteType.ENDPOINT
 
-        valid_keys: List[str] = ["url", "instance-name", "auth", 
"connection-config"]
+        valid_keys: List[str] = ["url", "protocol", "instance-name", "auth", 
"connection-config"]
         if not remote_execution:
             remote_type = cast(str, spec_node.get_enum("type", RemoteType, 
default=RemoteType.ALL))
             valid_keys += ["type"]
@@ -283,6 +301,8 @@ class RemoteSpec:
             provenance = spec_node.get_node("url").get_provenance()
             raise LoadError("{}: empty artifact cache URL".format(provenance), 
LoadErrorReason.INVALID_DATA)
 
+        protocol = spec_node.get_str("protocol", default="grpc")
+
         instance_name = spec_node.get_str("instance-name", default=None)
 
         auth_node = spec_node.get_mapping("auth", None)
@@ -296,6 +316,7 @@ class RemoteSpec:
         return cls(
             remote_type,
             url,
+            protocol=protocol,
             push=push,
             server_cert=server_cert,
             client_key=client_key,
@@ -327,6 +348,7 @@ class RemoteSpec:
     @classmethod
     def new_from_string(cls, string: str, purpose: int = 
RemoteSpecPurpose.ALL) -> "RemoteSpec":
         url: Optional[str] = None
+        protocol: Optional[str] = None
         instance_name: Optional[str] = None
         remote_type: str = RemoteType.ALL
         push: bool = True
@@ -353,6 +375,8 @@ class RemoteSpec:
 
                 if key == "url":
                     url = val
+                elif key == "protocol":
+                    protocol = val
                 elif key == "instance-name":
                     instance_name = val
                 elif key == "type":
@@ -396,6 +420,7 @@ class RemoteSpec:
         return cls(
             remote_type,
             url,
+            protocol=protocol,
             push=push,
             server_cert=server_cert,
             client_key=client_key,

Reply via email to