blob: 4a08fe27ffd9b5a5cf15592116b22c4f4f6174d9 [file]
// Copyright 2026 The Bazel Authors. All rights reserved.
//
// Licensed 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.
package com.google.devtools.build.lib.remote;
import static java.nio.charset.StandardCharsets.UTF_8;
import build.bazel.remote.asset.v1.FetchGrpc;
import build.bazel.remote.execution.v2.ActionCacheGrpc;
import build.bazel.remote.execution.v2.CapabilitiesGrpc;
import build.bazel.remote.execution.v2.ContentAddressableStorageGrpc;
import com.google.bytestream.ByteStreamGrpc;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.remote.options.RemoteOptions;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.Type;
import java.time.Duration;
import java.util.Map;
/** Builds Bazel's generated gRPC service config for remote services. */
public final class RemoteGrpcServiceConfig {
private static final Gson GSON = new Gson();
private static final Type MAP_TYPE = new TypeToken<Map<String, ?>>() {}.getType();
private static final ImmutableSet<String> SUPPORTED_TOP_LEVEL_FIELDS =
ImmutableSet.of("methodConfig");
private static final ImmutableSet<String> SUPPORTED_METHOD_CONFIG_FIELDS =
ImmutableSet.of("name", "timeout");
private static final ImmutableSet<String> SUPPORTED_NAME_FIELDS =
ImmutableSet.of("service", "method");
private RemoteGrpcServiceConfig() {}
public static ImmutableMap<String, ?> create(RemoteOptions options) {
return create(options.getRemoteTimeout());
}
public static ImmutableMap<String, ?> create(RemoteOptions options, Path workingDirectory)
throws IOException {
PathFragment serviceConfigPath = options.getRemoteGrpcServiceConfig();
if (serviceConfigPath == null) {
return create(options);
}
return parse(workingDirectory.getRelative(serviceConfigPath));
}
static ImmutableMap<String, ?> create(Duration remoteTimeout) {
return ImmutableMap.of(
"methodConfig",
ImmutableList.of(
ImmutableMap.of(
"name",
ImmutableList.of(
ImmutableMap.of("service", ActionCacheGrpc.SERVICE_NAME),
ImmutableMap.of("service", CapabilitiesGrpc.SERVICE_NAME),
ImmutableMap.of("service", ContentAddressableStorageGrpc.SERVICE_NAME),
ImmutableMap.of("service", ByteStreamGrpc.SERVICE_NAME),
ImmutableMap.of("service", FetchGrpc.SERVICE_NAME)),
"timeout",
remoteTimeout.toSeconds() + "s")));
}
private static ImmutableMap<String, ?> parse(Path serviceConfigPath) throws IOException {
try (Reader reader = new InputStreamReader(serviceConfigPath.getInputStream(), UTF_8)) {
JsonObject root = requireObject(JsonParser.parseReader(reader), "service config");
rejectUnsupportedFields(root, SUPPORTED_TOP_LEVEL_FIELDS, "service config");
rejectUnsupportedMethodConfigFields(root);
Map<String, ?> serviceConfig = GSON.fromJson(root, MAP_TYPE);
return ImmutableMap.copyOf(serviceConfig);
} catch (JsonParseException e) {
throw new IOException(
"failed to parse " + serviceConfigPath.getPathString() + ": " + e.getMessage(), e);
}
}
private static void rejectUnsupportedMethodConfigFields(JsonObject rootObject)
throws IOException {
JsonElement methodConfigsElement = rootObject.get("methodConfig");
if (methodConfigsElement == null || !methodConfigsElement.isJsonArray()) {
return;
}
JsonArray methodConfigs = methodConfigsElement.getAsJsonArray();
for (int i = 0; i < methodConfigs.size(); ++i) {
JsonElement methodConfigElement = methodConfigs.get(i);
if (!methodConfigElement.isJsonObject()) {
continue;
}
JsonObject methodConfig = methodConfigElement.getAsJsonObject();
String methodConfigPath = "methodConfig[" + i + "]";
rejectUnsupportedFields(methodConfig, SUPPORTED_METHOD_CONFIG_FIELDS, methodConfigPath);
rejectUnsupportedNameFields(methodConfig, methodConfigPath);
}
}
private static void rejectUnsupportedNameFields(JsonObject methodConfig, String methodConfigPath)
throws IOException {
JsonElement namesElement = methodConfig.get("name");
if (namesElement == null || !namesElement.isJsonArray()) {
return;
}
JsonArray names = namesElement.getAsJsonArray();
for (int i = 0; i < names.size(); ++i) {
JsonElement nameElement = names.get(i);
if (nameElement.isJsonObject()) {
rejectUnsupportedFields(
nameElement.getAsJsonObject(),
SUPPORTED_NAME_FIELDS,
methodConfigPath + ".name[" + i + "]");
}
}
}
private static void rejectUnsupportedFields(
JsonObject object, ImmutableSet<String> supportedFields, String path) throws IOException {
for (Map.Entry<String, JsonElement> entry : object.entrySet()) {
if (!supportedFields.contains(entry.getKey())) {
throw new IOException(path + " contains unsupported field '" + entry.getKey() + "'");
}
}
}
private static JsonObject requireObject(JsonElement element, String path) throws IOException {
if (element == null || !element.isJsonObject()) {
throw new IOException(path + " must be an object");
}
return element.getAsJsonObject();
}
}