Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/main/java/me/dinowernli/grpc/polyglot/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ public static void main(String[] args) {
ServiceList.listServices(
commandLineOutput,
fileDescriptorSet, config.getProtoConfig().getProtoDiscoveryRoot(),
arguments.serviceFilter(), arguments.methodFilter(), arguments.withMessage());
arguments.serviceFilter(), arguments.methodFilter(), arguments.withMessage(),
arguments.listOutputFormat());
break;

case CommandLineArgs.CALL_COMMAND:
Expand Down
1 change: 1 addition & 0 deletions src/main/java/me/dinowernli/grpc/polyglot/command/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ java_library(
"//src/main/java/me/dinowernli/grpc/polyglot/oauth2",
"//src/main/java/me/dinowernli/grpc/polyglot/protobuf",
"//src/main/proto:config_proto",
"//src/main/proto:output_proto",
"//third_party/google-oauth",
"//third_party/grpc",
"//third_party/guava",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ public static void callEndpoint(
dynamicClient = DynamicGrpcClient.create(methodDescriptor, hostAndPort, callConfig);
}

logger.info("Reading input from stdin");

ImmutableList<DynamicMessage> requestMessages =
MessageReader.forStdin(methodDescriptor.getInputType()).read();
StreamObserver<DynamicMessage> streamObserver =
Expand Down
100 changes: 73 additions & 27 deletions src/main/java/me/dinowernli/grpc/polyglot/command/ServiceList.java
Original file line number Diff line number Diff line change
@@ -1,55 +1,62 @@
package me.dinowernli.grpc.polyglot.command;

import java.io.File;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.protobuf.DescriptorProtos;
import com.google.protobuf.DescriptorProtos.FileDescriptorSet;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.MethodDescriptor;
import com.google.protobuf.Descriptors.ServiceDescriptor;
import com.google.protobuf.util.JsonFormat;

import me.dinowernli.grpc.polyglot.io.MessageWriter;
import me.dinowernli.grpc.polyglot.io.Output;
import me.dinowernli.grpc.polyglot.protobuf.ServiceResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import polyglot.OutputProto.ListServicesJsonOutput;

/** Utility to list the services, methods and message definitions for the known GRPC end-points */
public class ServiceList {
private static final Logger logger = LoggerFactory.getLogger(MessageWriter.class);

/** Lists the GRPC services - filtered by service name (contains) or method name (contains) */
public static void listServices(
Output output,
FileDescriptorSet fileDescriptorSet,
String protoDiscoveryRoot,
Optional<String> serviceFilter,
Optional<String> methodFilter,
Optional<Boolean> withMessage) {
public static void listServices(Output output, FileDescriptorSet fileDescriptorSet, String protoDiscoveryRoot,
Optional<String> serviceFilter, Optional<String> methodFilter, Optional<Boolean> withMessage,
Optional<String> listOutputFormat) {

ServiceResolver serviceResolver = ServiceResolver.fromFileDescriptorSet(fileDescriptorSet);

// Add white-space before the rendered output
output.newLine();
if (listOutputFormat.isPresent() && listOutputFormat.get().equals("json")) {
printJsonOutput(output, serviceResolver, serviceFilter, methodFilter);
} else {

// Add white-space before the rendered output
output.newLine();

for (ServiceDescriptor descriptor : serviceResolver.listServices()) {
boolean matchingDescriptor =
!serviceFilter.isPresent()
|| descriptor.getFullName().toLowerCase().contains(serviceFilter.get().toLowerCase());
for (ServiceDescriptor descriptor : serviceResolver.listServices()) {
boolean matchingDescriptor = !serviceFilter.isPresent()
|| descriptor.getFullName().toLowerCase().contains(serviceFilter.get().toLowerCase());

if (matchingDescriptor) {
listMethods(output, protoDiscoveryRoot, descriptor, methodFilter, withMessage);
if (matchingDescriptor) {
listMethods(output, protoDiscoveryRoot, descriptor, methodFilter, withMessage);
}
}
}
}

/** Lists the methods on the service (the methodFilter will be applied if non-empty) */
private static void listMethods(
Output output,
String protoDiscoveryRoot,
ServiceDescriptor descriptor,
Optional<String> methodFilter,
Optional<Boolean> withMessage) {
private static void listMethods(Output output, String protoDiscoveryRoot, ServiceDescriptor descriptor,
Optional<String> methodFilter, Optional<Boolean> withMessage) {

boolean printedService = false;

Expand All @@ -58,7 +65,7 @@ private static void listMethods(
File protoDiscoveryDir = new File(protoDiscoveryRoot).getParentFile();

for (MethodDescriptor method : descriptor.getMethods()) {
if (!methodFilter.isPresent() || method.getName().contains(methodFilter.get())) {
if (!methodFilter.isPresent() || method.getName().toLowerCase().contains(methodFilter.get().toLowerCase())) {

// Only print the service name once - and only if a method is going to be printed
if (!printedService) {
Expand Down Expand Up @@ -88,8 +95,7 @@ private static String renderDescriptor(Descriptor descriptor, String indent) {
return indent + "<empty>";
}

List<String> fieldsAsStrings = descriptor.getFields().stream()
.map(field -> renderDescriptor(field, indent + " "))
List<String> fieldsAsStrings = descriptor.getFields().stream().map(field -> renderDescriptor(field, indent + " "))
.collect(Collectors.toList());

return Joiner.on(System.lineSeparator()).join(fieldsAsStrings);
Expand All @@ -102,8 +108,7 @@ private static String renderDescriptor(FieldDescriptor descriptor, String indent
String fieldPrefix = indent + descriptor.getJsonName() + "[" + isOpt + " " + isRep + "]";

if (descriptor.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
return fieldPrefix + " {" + System.lineSeparator()
+ renderDescriptor(descriptor.getMessageType(), indent + " ")
return fieldPrefix + " {" + System.lineSeparator() + renderDescriptor(descriptor.getMessageType(), indent + " ")
+ System.lineSeparator() + indent + "}";

} else if (descriptor.getJavaType() == FieldDescriptor.JavaType.ENUM) {
Expand All @@ -113,4 +118,45 @@ private static String renderDescriptor(FieldDescriptor descriptor, String indent
return fieldPrefix + ": " + descriptor.getJavaType();
}
}
}

private static void printJsonOutput(Output output, ServiceResolver serviceResolver, Optional<String> serviceFilter,
Optional<String> methodFilter) {
ListServicesJsonOutput.Builder listServicesJsonOutputBuilder = ListServicesJsonOutput.newBuilder();

// Filter service descriptors (case insensitive)
ImmutableList<ServiceDescriptor> serviceDescriptors = ImmutableList
.copyOf(Lists.newArrayList(serviceResolver.listServices()).stream()
.filter(serviceDescriptor -> !serviceFilter.isPresent()
|| serviceDescriptor.getFullName().toLowerCase().contains(serviceFilter.get().toLowerCase()))
.collect(Collectors.toList()));

ImmutableList<DescriptorProtos.ServiceDescriptorProto> serviceDescriptorProtos = ImmutableList
.copyOf(serviceDescriptors.stream().map(serviceDescriptor -> serviceDescriptor.toProto().toBuilder()
// filtering methods by clearing the methods then readding back only the filtered ones
.clearMethod()
.addAllMethod(serviceDescriptor.getMethods().stream()
.filter(methodDescriptor -> !methodFilter.isPresent()
|| methodDescriptor.getName().toLowerCase().contains(methodFilter.get().toLowerCase()))
.map(methodDescriptor -> methodDescriptor.toProto()).collect(Collectors.toList()))
.build()).collect(Collectors.toList()));

listServicesJsonOutputBuilder.addAllServices(serviceDescriptorProtos);

Set<DescriptorProtos.FileDescriptorProto> fileProtosSet = new HashSet<>();

serviceDescriptors.forEach(serviceDescriptor -> {
fileProtosSet.add(serviceDescriptor.getFile().toProto());
serviceDescriptor.getFile().getDependencies().stream()
.forEach(fileDescriptor -> fileProtosSet.add(fileDescriptor.toProto()));
});

listServicesJsonOutputBuilder.addAllDependencies(fileProtosSet);

try {
String jsonOut = JsonFormat.printer().print(listServicesJsonOutputBuilder.build());
output.writeLine(jsonOut);
} catch (com.google.protobuf.InvalidProtocolBufferException e) {
logger.error("Error printing JSON output.", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
Expand Down Expand Up @@ -56,6 +58,21 @@ public class CommandLineArgs {
@Option(name = "--tls_client_override_authority", metaVar = "<host>")
private String tlsClientOverrideAuthority;

@Option(name = "--oauth_refresh_token_endpoint_url", metaVar = "<url>")
private String oauthRefreshTokenEndpointUrl;

@Option(name = "--oauth_client_id", metaVar = "<client-id>")
private String oauthClientId;

@Option(name = "--oauth_client_secret", metaVar = "<client-secret>")
private String oauthClientSecret;

@Option(name = "--oauth_refresh_token_path", metaVar = "<path>")
private String oauthRefreshTokenPath;

@Option(name = "--oauth_access_token_path", metaVar = "<path>")
private String oauthAccessTokenPath;

@Option(name = "--help")
private Boolean help;

Expand All @@ -74,25 +91,32 @@ public class CommandLineArgs {

// TODO: Move to a "list_services"-specific flag container
@Option(
name = "--service_filter",
metaVar = "service_name",
usage="Filters service names containing this string e.g. --service_filter TestService")
name = "--service_filter",
metaVar = "service_name",
usage="Filters service names containing this string e.g. --service_filter TestService")
private String serviceFilterArg;

// TODO: Move to a "list_services"-specific flag container
@Option(
name = "--method_filter",
metaVar = "method_name",
usage="Filters service methods to those containing this string e.g. --method_name List")
name = "--method_filter",
metaVar = "method_name",
usage="Filters service methods to those containing this string e.g. --method_name List")
private String methodFilterArg;

//TODO: Move to a "list_services"-specific flag container
@Option(
name = "--with_message",
metaVar = "true|false",
usage="If true, then the message specification for the method is rendered")
name = "--with_message",
metaVar = "true|false",
usage="If true, then the message specification for the method is rendered")
private String withMessageArg;

//TODO: Move to a "list_services"-specific flag container
@Option(
name = "--list_output_format",
metaVar = "readable|json",
usage="The output format of the list service command, defaults to readable if arg is omitted ")
private String listOutputFormatArg;

// *************************************************************************

/**
Expand Down Expand Up @@ -174,6 +198,26 @@ public Optional<String> tlsClientOverrideAuthority() {
return Optional.ofNullable(tlsClientOverrideAuthority);
}

public Optional<URL> oauthRefreshTokenEndpointUrl() {
return maybeUrl(oauthRefreshTokenEndpointUrl);
}

public Optional<String> oauthClientId() {
return Optional.ofNullable(oauthClientId);
}

public Optional<String> oauthClientSecret() {
return Optional.ofNullable(oauthClientSecret);
}

public Optional<Path> oauthRefreshTokenPath() {
return maybePath(oauthRefreshTokenPath);
}

public Optional<Path> oauthAccessTokenPath() {
return maybePath(oauthAccessTokenPath);
}

/**
* First stage of a migration towards a "command"-based instantiation of polyglot.
* Supported commands:
Expand Down Expand Up @@ -203,6 +247,12 @@ public Optional<Boolean> withMessage() {
}
return Optional.of(Boolean.parseBoolean(withMessageArg));
}

//TODO: Move to a "list_services"-specific flag container
public Optional<String> listOutputFormat() {
return Optional.ofNullable(listOutputFormatArg);
}

// *************************************************************************

public ImmutableList<Path> additionalProtocIncludes() {
Expand Down Expand Up @@ -235,4 +285,17 @@ private static Optional<Path> maybePath(String rawPath) {
Preconditions.checkArgument(Files.exists(path), "File " + rawPath + " does not exist");
return Optional.of(Paths.get(rawPath));
}

private static Optional<URL> maybeUrl(String rawUrl) {
if (rawUrl == null) {
return Optional.empty();
}
try {
URL url = new URL(rawUrl);
return Optional.of(url);
} catch (MalformedURLException e) {
throw new IllegalArgumentException("URL " + rawUrl + " is invalid", e);
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,9 @@ private Configuration getDefaultConfigurationInternal() {
private Configuration getNamedConfigurationInternal(String name) {
Preconditions.checkState(!isEmptyConfig(), "Cannot load named config with a config set");
return configSet.get().getConfigurationsList().stream()
.filter(config -> config.getName().equals(name))
.findAny()
.orElseThrow(() -> new IllegalArgumentException("Could not find named config: " + name));
.filter(config -> config.getName().equals(name))
.findAny()
.orElseThrow(() -> new IllegalArgumentException("Could not find named config: " + name));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: indentation here was correct, please revert

}

/** Returns the {@link Configuration} with overrides, if any, applied to it. */
Expand All @@ -111,7 +111,7 @@ private Configuration applyOverrides(Configuration configuration) {
if (overrides.get().outputFilePath().isPresent()) {
resultBuilder.getOutputConfigBuilder().setDestination(Destination.FILE);
resultBuilder.getOutputConfigBuilder().setFilePath(
overrides.get().outputFilePath().get().toString());
overrides.get().outputFilePath().get().toString());
}
if (!overrides.get().additionalProtocIncludes().isEmpty()) {
List<String> additionalIncludes = new ArrayList<>();
Expand All @@ -122,7 +122,7 @@ private Configuration applyOverrides(Configuration configuration) {
}
if (overrides.get().protoDiscoveryRoot().isPresent()) {
resultBuilder.getProtoConfigBuilder().setProtoDiscoveryRoot(
overrides.get().protoDiscoveryRoot().get().toString());
overrides.get().protoDiscoveryRoot().get().toString());
}
if (overrides.get().getRpcDeadlineMs().isPresent()) {
resultBuilder.getCallConfigBuilder().setDeadlineMs(overrides.get().getRpcDeadlineMs().get());
Expand All @@ -141,7 +141,30 @@ private Configuration applyOverrides(Configuration configuration) {
}
if (overrides.get().tlsClientOverrideAuthority().isPresent()) {
resultBuilder.getCallConfigBuilder().setTlsClientOverrideAuthority(
overrides.get().tlsClientOverrideAuthority().get());
overrides.get().tlsClientOverrideAuthority().get());
}
if (overrides.get().oauthRefreshTokenEndpointUrl().isPresent()) {
resultBuilder.getCallConfigBuilder().getOauthConfigBuilder().getRefreshTokenCredentialsBuilder()
.setTokenEndpointUrl(overrides.get().oauthRefreshTokenEndpointUrl().get().toString());
}
if (overrides.get().oauthClientId().isPresent()) {
resultBuilder.getCallConfigBuilder().getOauthConfigBuilder().getRefreshTokenCredentialsBuilder()
.getClientBuilder().setId(overrides.get().oauthClientId().get());
}
if (overrides.get().oauthClientSecret().isPresent()) {
resultBuilder.getCallConfigBuilder().getOauthConfigBuilder().getRefreshTokenCredentialsBuilder()
.getClientBuilder().setSecret(overrides.get().oauthClientSecret().get());
}
if (overrides.get().oauthRefreshTokenPath().isPresent()) {
resultBuilder.getCallConfigBuilder().getOauthConfigBuilder().getRefreshTokenCredentialsBuilder()
.setRefreshTokenPath(overrides.get().oauthRefreshTokenPath().get().toString());
}
// Note the ordering of setting these fields is important. Oauth configuration has a oneof field, corresponding
// to access or refresh tokens. We want access tokens to take precedence, setting this field last will ensure this
// occurs. See https://developers.google.com/protocol-buffers/docs/proto#oneof
if (overrides.get().oauthAccessTokenPath().isPresent()) {
resultBuilder.getCallConfigBuilder().getOauthConfigBuilder().getAccessTokenCredentialsBuilder()
.setAccessTokenPath(overrides.get().oauthAccessTokenPath().get().toString());
}
return resultBuilder.build();
}
Expand Down
Loading