diff --git a/src/main/java/me/dinowernli/grpc/polyglot/Main.java b/src/main/java/me/dinowernli/grpc/polyglot/Main.java index 9d7b80a..e2a403f 100644 --- a/src/main/java/me/dinowernli/grpc/polyglot/Main.java +++ b/src/main/java/me/dinowernli/grpc/polyglot/Main.java @@ -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: diff --git a/src/main/java/me/dinowernli/grpc/polyglot/command/BUILD b/src/main/java/me/dinowernli/grpc/polyglot/command/BUILD index c57d621..bf402b5 100644 --- a/src/main/java/me/dinowernli/grpc/polyglot/command/BUILD +++ b/src/main/java/me/dinowernli/grpc/polyglot/command/BUILD @@ -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", diff --git a/src/main/java/me/dinowernli/grpc/polyglot/command/ServiceCall.java b/src/main/java/me/dinowernli/grpc/polyglot/command/ServiceCall.java index 54313b8..7ef76b0 100644 --- a/src/main/java/me/dinowernli/grpc/polyglot/command/ServiceCall.java +++ b/src/main/java/me/dinowernli/grpc/polyglot/command/ServiceCall.java @@ -71,6 +71,8 @@ public static void callEndpoint( dynamicClient = DynamicGrpcClient.create(methodDescriptor, hostAndPort, callConfig); } + logger.info("Reading input from stdin"); + ImmutableList requestMessages = MessageReader.forStdin(methodDescriptor.getInputType()).read(); StreamObserver streamObserver = diff --git a/src/main/java/me/dinowernli/grpc/polyglot/command/ServiceList.java b/src/main/java/me/dinowernli/grpc/polyglot/command/ServiceList.java index ab557d5..be49816 100644 --- a/src/main/java/me/dinowernli/grpc/polyglot/command/ServiceList.java +++ b/src/main/java/me/dinowernli/grpc/polyglot/command/ServiceList.java @@ -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 serviceFilter, - Optional methodFilter, - Optional withMessage) { + public static void listServices(Output output, FileDescriptorSet fileDescriptorSet, String protoDiscoveryRoot, + Optional serviceFilter, Optional methodFilter, Optional withMessage, + Optional 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 methodFilter, - Optional withMessage) { + private static void listMethods(Output output, String protoDiscoveryRoot, ServiceDescriptor descriptor, + Optional methodFilter, Optional withMessage) { boolean printedService = false; @@ -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) { @@ -88,8 +95,7 @@ private static String renderDescriptor(Descriptor descriptor, String indent) { return indent + ""; } - List fieldsAsStrings = descriptor.getFields().stream() - .map(field -> renderDescriptor(field, indent + " ")) + List fieldsAsStrings = descriptor.getFields().stream().map(field -> renderDescriptor(field, indent + " ")) .collect(Collectors.toList()); return Joiner.on(System.lineSeparator()).join(fieldsAsStrings); @@ -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) { @@ -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 serviceFilter, + Optional methodFilter) { + ListServicesJsonOutput.Builder listServicesJsonOutputBuilder = ListServicesJsonOutput.newBuilder(); + + // Filter service descriptors (case insensitive) + ImmutableList serviceDescriptors = ImmutableList + .copyOf(Lists.newArrayList(serviceResolver.listServices()).stream() + .filter(serviceDescriptor -> !serviceFilter.isPresent() + || serviceDescriptor.getFullName().toLowerCase().contains(serviceFilter.get().toLowerCase())) + .collect(Collectors.toList())); + + ImmutableList 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 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); + } + } +} \ No newline at end of file diff --git a/src/main/java/me/dinowernli/grpc/polyglot/config/CommandLineArgs.java b/src/main/java/me/dinowernli/grpc/polyglot/config/CommandLineArgs.java index f8622f8..7053373 100644 --- a/src/main/java/me/dinowernli/grpc/polyglot/config/CommandLineArgs.java +++ b/src/main/java/me/dinowernli/grpc/polyglot/config/CommandLineArgs.java @@ -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; @@ -56,6 +58,21 @@ public class CommandLineArgs { @Option(name = "--tls_client_override_authority", metaVar = "") private String tlsClientOverrideAuthority; + @Option(name = "--oauth_refresh_token_endpoint_url", metaVar = "") + private String oauthRefreshTokenEndpointUrl; + + @Option(name = "--oauth_client_id", metaVar = "") + private String oauthClientId; + + @Option(name = "--oauth_client_secret", metaVar = "") + private String oauthClientSecret; + + @Option(name = "--oauth_refresh_token_path", metaVar = "") + private String oauthRefreshTokenPath; + + @Option(name = "--oauth_access_token_path", metaVar = "") + private String oauthAccessTokenPath; + @Option(name = "--help") private Boolean help; @@ -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; + // ************************************************************************* /** @@ -174,6 +198,26 @@ public Optional tlsClientOverrideAuthority() { return Optional.ofNullable(tlsClientOverrideAuthority); } + public Optional oauthRefreshTokenEndpointUrl() { + return maybeUrl(oauthRefreshTokenEndpointUrl); + } + + public Optional oauthClientId() { + return Optional.ofNullable(oauthClientId); + } + + public Optional oauthClientSecret() { + return Optional.ofNullable(oauthClientSecret); + } + + public Optional oauthRefreshTokenPath() { + return maybePath(oauthRefreshTokenPath); + } + + public Optional oauthAccessTokenPath() { + return maybePath(oauthAccessTokenPath); + } + /** * First stage of a migration towards a "command"-based instantiation of polyglot. * Supported commands: @@ -203,6 +247,12 @@ public Optional withMessage() { } return Optional.of(Boolean.parseBoolean(withMessageArg)); } + + //TODO: Move to a "list_services"-specific flag container + public Optional listOutputFormat() { + return Optional.ofNullable(listOutputFormatArg); + } + // ************************************************************************* public ImmutableList additionalProtocIncludes() { @@ -235,4 +285,17 @@ private static Optional maybePath(String rawPath) { Preconditions.checkArgument(Files.exists(path), "File " + rawPath + " does not exist"); return Optional.of(Paths.get(rawPath)); } + + private static Optional 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); + } + + } } diff --git a/src/main/java/me/dinowernli/grpc/polyglot/config/ConfigurationLoader.java b/src/main/java/me/dinowernli/grpc/polyglot/config/ConfigurationLoader.java index e927912..ed0d286 100644 --- a/src/main/java/me/dinowernli/grpc/polyglot/config/ConfigurationLoader.java +++ b/src/main/java/me/dinowernli/grpc/polyglot/config/ConfigurationLoader.java @@ -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)); } /** Returns the {@link Configuration} with overrides, if any, applied to it. */ @@ -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 additionalIncludes = new ArrayList<>(); @@ -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()); @@ -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(); } diff --git a/src/main/java/me/dinowernli/grpc/polyglot/io/MessageWriter.java b/src/main/java/me/dinowernli/grpc/polyglot/io/MessageWriter.java index e13341a..3f32a04 100644 --- a/src/main/java/me/dinowernli/grpc/polyglot/io/MessageWriter.java +++ b/src/main/java/me/dinowernli/grpc/polyglot/io/MessageWriter.java @@ -19,61 +19,61 @@ * {@link Output}. The messages are writting in a newline-separated json format. */ public class MessageWriter implements StreamObserver { - private static final Logger logger = LoggerFactory.getLogger(MessageWriter.class); + private static final Logger logger = LoggerFactory.getLogger(MessageWriter.class); - /** Used to separate the individual plaintext json proto messages. */ - private static final String MESSAGE_SEPARATOR = "\n\n"; + /** Used to separate the individual plaintext json proto messages. */ + private static final String MESSAGE_SEPARATOR = "\n\n"; - private final JsonFormat.Printer jsonPrinter; - private final Output output; + private final JsonFormat.Printer jsonPrinter; + private final Output output; - /** - * Creates a new {@link MessageWriter} which writes the messages it sees to the supplied - * {@link Output}. - */ - public static MessageWriter create(Output output) { - return new MessageWriter(JsonFormat.printer(), output); - } + /** + * Creates a new {@link MessageWriter} which writes the messages it sees to the supplied + * {@link Output}. + */ + public static MessageWriter create(Output output) { + return new MessageWriter(JsonFormat.printer(), output); + } - /** - * Returns the string representation of the stream of supplied messages. Each individual message - * is represented as valid json, but not that the whole result is, itself, *not* valid json. - */ - public static String writeJsonStream(ImmutableList messages) { - ByteArrayOutputStream resultStream = new ByteArrayOutputStream(); - MessageWriter writer = MessageWriter.create(Output.forStream(new PrintStream(resultStream))); - writer.writeAll(messages); - return resultStream.toString(); - } + /** + * Returns the string representation of the stream of supplied messages. Each individual message + * is represented as valid json, but not that the whole result is, itself, *not* valid json. + */ + public static String writeJsonStream(ImmutableList messages) { + ByteArrayOutputStream resultStream = new ByteArrayOutputStream(); + MessageWriter writer = MessageWriter.create(Output.forStream(new PrintStream(resultStream))); + writer.writeAll(messages); + return resultStream.toString(); + } - @VisibleForTesting - MessageWriter(JsonFormat.Printer jsonPrinter, Output output) { - this.jsonPrinter = jsonPrinter; - this.output = output; - } + @VisibleForTesting + MessageWriter(JsonFormat.Printer jsonPrinter, Output output) { + this.jsonPrinter = jsonPrinter; + this.output = output; + } - @Override - public void onCompleted() { - // Nothing to do. - } + @Override + public void onCompleted() { + // Nothing to do. + } - @Override - public void onError(Throwable t) { - // Nothing to do. - } + @Override + public void onError(Throwable t) { + // Nothing to do. + } - @Override - public void onNext(T message) { - try { - output.write(jsonPrinter.print(message) + MESSAGE_SEPARATOR); - } catch (InvalidProtocolBufferException e) { - logger.error("Skipping invalid response message", e); + @Override + public void onNext(T message) { + try { + output.write(jsonPrinter.print(message) + MESSAGE_SEPARATOR); + } catch (InvalidProtocolBufferException e) { + logger.error("Skipping invalid response message", e); + } } - } - /** Writes all the supplied messages and closes the stream. */ - public void writeAll(ImmutableList messages) { - messages.forEach(this::onNext); - onCompleted(); - } + /** Writes all the supplied messages and closes the stream. */ + public void writeAll(ImmutableList messages) { + messages.forEach(this::onNext); + onCompleted(); + } } diff --git a/src/main/java/me/dinowernli/grpc/polyglot/oauth2/RefreshTokenCredentials.java b/src/main/java/me/dinowernli/grpc/polyglot/oauth2/RefreshTokenCredentials.java index b27b4e9..7140504 100644 --- a/src/main/java/me/dinowernli/grpc/polyglot/oauth2/RefreshTokenCredentials.java +++ b/src/main/java/me/dinowernli/grpc/polyglot/oauth2/RefreshTokenCredentials.java @@ -71,10 +71,10 @@ public AccessToken refreshAccessToken() throws IOException { logger.info("Refresh successful, got access token"); return new AccessToken( refreshResponse.getAccessToken(), - computeExpirtyDate(refreshResponse.getExpiresInSeconds())); + computeExpiryDate(refreshResponse.getExpiresInSeconds())); } - private Date computeExpirtyDate(long expiresInSeconds) { + private Date computeExpiryDate(long expiresInSeconds) { long expiresInSecondsWithMargin = (long) (expiresInSeconds * ACCESS_TOKEN_EXPIRY_MARGIN); return Date.from(clock.instant().plusSeconds(expiresInSecondsWithMargin)); } diff --git a/src/main/proto/BUILD b/src/main/proto/BUILD index d4fa5ff..4a3ee98 100644 --- a/src/main/proto/BUILD +++ b/src/main/proto/BUILD @@ -7,6 +7,13 @@ java_proto_library( protos = ["config.proto"], ) +java_proto_library( + name = "output_proto", + protos = ["output.proto"], + imports = ["external/com_github_google_protobuf/src/"], + inputs = ["@com_github_google_protobuf//:well_known_protos"], +) + java_proto_library( name = "hello_proto_grpc", protos = ["hello.proto"], diff --git a/src/main/proto/output.proto b/src/main/proto/output.proto new file mode 100644 index 0000000..bb09338 --- /dev/null +++ b/src/main/proto/output.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +import "google/protobuf/descriptor.proto"; + +package polyglot; + +option java_outer_classname = "OutputProto"; + +message ListServicesJsonOutput { + repeated google.protobuf.ServiceDescriptorProto services = 1; + repeated google.protobuf.FileDescriptorProto dependencies = 2; +} \ No newline at end of file diff --git a/src/test/java/me/dinowernli/grpc/polyglot/command/BUILD b/src/test/java/me/dinowernli/grpc/polyglot/command/BUILD index 9bc6ac7..cc71070 100644 --- a/src/test/java/me/dinowernli/grpc/polyglot/command/BUILD +++ b/src/test/java/me/dinowernli/grpc/polyglot/command/BUILD @@ -9,6 +9,7 @@ auto_java_test( "//src/main/java/me/dinowernli/grpc/polyglot/testing", "//src/main/proto/testing:test_service_proto", "//src/main/proto/testing/foo:foo_proto", + "//src/main/proto:output_proto", "//third_party/guava", "//third_party/protobuf", "//third_party/testing", diff --git a/src/test/java/me/dinowernli/grpc/polyglot/command/ServiceListTest.java b/src/test/java/me/dinowernli/grpc/polyglot/command/ServiceListTest.java index ef6faac..6c5bb8b 100644 --- a/src/test/java/me/dinowernli/grpc/polyglot/command/ServiceListTest.java +++ b/src/test/java/me/dinowernli/grpc/polyglot/command/ServiceListTest.java @@ -17,13 +17,13 @@ @TestClass public class ServiceListTest { private static FileDescriptorSet PROTO_FILE_DESCRIPTORS = FileDescriptorSet.newBuilder() - .addFile(TestProto.getDescriptor().toProto()) - .addFile(FooProto.getDescriptor().toProto()) - .build(); + .addFile(TestProto.getDescriptor().toProto()) + .addFile(FooProto.getDescriptor().toProto()) + .build(); private static final String EXPECTED_SERVICE = "polyglot.test.TestService"; private static final ImmutableList EXPECTED_METHOD_NAMES = ImmutableList.of( - "TestMethod", "TestMethodStream", "TestMethodClientStream", "TestMethodBidi"); + "TestMethod", "TestMethodStream", "TestMethodClientStream", "TestMethodBidi"); private RecordingOutput recordingOutput; @@ -35,12 +35,13 @@ public void setUp() throws Throwable { @Test public void testServiceListOutput() throws Throwable { ServiceList.listServices( - recordingOutput, - PROTO_FILE_DESCRIPTORS, - "", - Optional.empty(), - Optional.empty(), - Optional.empty()); + recordingOutput, + PROTO_FILE_DESCRIPTORS, + "", + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty()); recordingOutput.close(); validateOutput(recordingOutput.getContentsAsString(), EXPECTED_SERVICE, EXPECTED_METHOD_NAMES); @@ -49,12 +50,13 @@ public void testServiceListOutput() throws Throwable { @Test public void testServiceListOutputWithServiceFilter() throws Throwable { ServiceList.listServices( - recordingOutput, - PROTO_FILE_DESCRIPTORS, - "", - Optional.of("TestService"), - Optional.empty(), - Optional.empty()); + recordingOutput, + PROTO_FILE_DESCRIPTORS, + "", + Optional.of("TestService"), + Optional.empty(), + Optional.empty(), + Optional.empty()); recordingOutput.close(); validateOutput(recordingOutput.getContentsAsString(), EXPECTED_SERVICE, EXPECTED_METHOD_NAMES); @@ -63,34 +65,51 @@ public void testServiceListOutputWithServiceFilter() throws Throwable { @Test public void testServiceListOutputWithMethodFilter() throws Throwable { ServiceList.listServices( - recordingOutput, - PROTO_FILE_DESCRIPTORS, - "", - Optional.of("TestService"), - Optional.of("TestMethodStream"), - Optional.empty()); + recordingOutput, + PROTO_FILE_DESCRIPTORS, + "", + Optional.of("TestService"), + Optional.of("TestMethodStream"), + Optional.empty(), + Optional.empty()); recordingOutput.close(); validateOutput( - recordingOutput.getContentsAsString(), - EXPECTED_SERVICE, - ImmutableList.of("TestMethodStream")); + recordingOutput.getContentsAsString(), + EXPECTED_SERVICE, + ImmutableList.of("TestMethodStream")); } @Test public void testServiceListOutputWithMessageDetail() throws Throwable { ServiceList.listServices( - recordingOutput, - PROTO_FILE_DESCRIPTORS, - "", - Optional.of("TestService"), - Optional.of("TestMethodStream"), - Optional.of(true)); + recordingOutput, + PROTO_FILE_DESCRIPTORS, + "", + Optional.of("TestService"), + Optional.of("TestMethodStream"), + Optional.of(true), + Optional.empty()); recordingOutput.close(); validateMessageOutput(recordingOutput.getContentsAsString()); } + @Test + public void testServiceListOutputWithMessageDetailJsonFormatted() throws Throwable { + ServiceList.listServices( + recordingOutput, + PROTO_FILE_DESCRIPTORS, + "", + Optional.of("TestService"), + Optional.of("TestMethodStream"), + Optional.of(true), + Optional.of("json")); + recordingOutput.close(); + + validateJsonMessageOutput(recordingOutput.getContentsAsString()); + } + /** Compares the actual output with the expected output format */ private void validateOutput( String output, String serviceName, ImmutableList methodNames) { @@ -134,14 +153,123 @@ private void validateMessageOutput(String output) { assertThat(lines[0]).startsWith("polyglot.test.TestService -> "); ImmutableList expectedLines = ImmutableList.of( - "polyglot.test.TestService/TestMethodStream", - "message[ ]: STRING", - "foo[ ] {", - "message[ ]: STRING", - "}"); + "polyglot.test.TestService/TestMethodStream", + "message[ ]: STRING", + "foo[ ] {", + "message[ ]: STRING", + "}"); for (int i = 0; i < expectedLines.size(); i++) { assertThat(lines[i + 1].trim()).isEqualTo(expectedLines.get(i)); } } + + /** Ensures that the message-rendering logic is correct */ + private void validateJsonMessageOutput(String output) { + String[] lines = output.trim().split("\n"); + + for (int i = 0; i < JSON_GOLD.size(); i++) { + assertThat(lines[i].trim()).isEqualTo(JSON_GOLD.get(i)); + } + } + + private static final ImmutableList JSON_GOLD = ImmutableList.of( + "{", + "\"services\": [{", + "\"name\": \"TestService\",", + "\"method\": [{", + "\"name\": \"TestMethodStream\",", + "\"inputType\": \".polyglot.test.TestRequest\",", + "\"outputType\": \".polyglot.test.TestResponse\",", + "\"options\": {", + "},", + "\"serverStreaming\": true", + "}]", + "}],", + "\"dependencies\": [{", + "\"name\": \"src/main/proto/testing/test_service.proto\",", + "\"package\": \"polyglot.test\",", + "\"dependency\": [\"src/main/proto/testing/foo/foo.proto\"],", + "\"messageType\": [{", + "\"name\": \"TestRequest\",", + "\"field\": [{", + "\"name\": \"message\",", + "\"number\": 1,", + "\"label\": \"LABEL_OPTIONAL\",", + "\"type\": \"TYPE_STRING\"", + "}, {", + "\"name\": \"foo\",", + "\"number\": 2,", + "\"label\": \"LABEL_OPTIONAL\",", + "\"type\": \"TYPE_MESSAGE\",", + "\"typeName\": \".polyglot.test.foo.Foo\"", + "}, {", + "\"name\": \"number\",", + "\"number\": 3,", + "\"label\": \"LABEL_OPTIONAL\",", + "\"type\": \"TYPE_INT32\"", + "}]", + "}, {", + "\"name\": \"TestResponse\",", + "\"field\": [{", + "\"name\": \"message\",", + "\"number\": 1,", + "\"label\": \"LABEL_OPTIONAL\",", + "\"type\": \"TYPE_STRING\"", + "}]", + "}],", + "\"service\": [{", + "\"name\": \"TestService\",", + "\"method\": [{", + "\"name\": \"TestMethod\",", + "\"inputType\": \".polyglot.test.TestRequest\",", + "\"outputType\": \".polyglot.test.TestResponse\",", + "\"options\": {", + "}", + "}, {", + "\"name\": \"TestMethodStream\",", + "\"inputType\": \".polyglot.test.TestRequest\",", + "\"outputType\": \".polyglot.test.TestResponse\",", + "\"options\": {", + "},", + "\"serverStreaming\": true", + "}, {", + "\"name\": \"TestMethodClientStream\",", + "\"inputType\": \".polyglot.test.TestRequest\",", + "\"outputType\": \".polyglot.test.TestResponse\",", + "\"options\": {", + "},", + "\"clientStreaming\": true", + "}, {", + "\"name\": \"TestMethodBidi\",", + "\"inputType\": \".polyglot.test.TestRequest\",", + "\"outputType\": \".polyglot.test.TestResponse\",", + "\"options\": {", + "},", + "\"clientStreaming\": true,", + "\"serverStreaming\": true", + "}]", + "}],", + "\"options\": {", + "\"javaOuterClassname\": \"TestProto\"", + "},", + "\"syntax\": \"proto3\"", + "}, {", + "\"name\": \"src/main/proto/testing/foo/foo.proto\",", + "\"package\": \"polyglot.test.foo\",", + "\"messageType\": [{", + "\"name\": \"Foo\",", + "\"field\": [{", + "\"name\": \"message\",", + "\"number\": 1,", + "\"label\": \"LABEL_OPTIONAL\",", + "\"type\": \"TYPE_STRING\"", + "}]", + "}],", + "\"options\": {", + "\"javaOuterClassname\": \"FooProto\"", + "},", + "\"syntax\": \"proto3\"", + "}]", + "}"); } diff --git a/src/test/java/me/dinowernli/grpc/polyglot/config/CommandLineArgsTest.java b/src/test/java/me/dinowernli/grpc/polyglot/config/CommandLineArgsTest.java index e268b27..71169e9 100644 --- a/src/test/java/me/dinowernli/grpc/polyglot/config/CommandLineArgsTest.java +++ b/src/test/java/me/dinowernli/grpc/polyglot/config/CommandLineArgsTest.java @@ -34,24 +34,62 @@ public void tearDown() throws Throwable { @Test public void parsesAdditionalIncludesSingle() { CommandLineArgs params = parseArgs(ImmutableList.of( - String.format("--add_protoc_includes=%s,%s", tempFile1.toString(), tempFile2.toString()))); + String.format("--add_protoc_includes=%s,%s", tempFile1.toString(), tempFile2.toString()))); assertThat(params.additionalProtocIncludes()).hasSize(2); } @Test public void parsesAdditionalIncludesMulti() { CommandLineArgs params = parseArgs(ImmutableList.of( - String.format("--add_protoc_includes=%s", tempFile1.toString()))); + String.format("--add_protoc_includes=%s", tempFile1.toString()))); assertThat(params.additionalProtocIncludes()).hasSize(1); } + @Test + public void parsesOauthRefreshTokenEndpointUrl() { + String url = "https://github.com/grpc-ecosystem/polyglot"; + CommandLineArgs params = parseArgs(ImmutableList.of( + String.format("--oauth_refresh_token_endpoint_url=%s", url))); + assertThat(params.oauthRefreshTokenEndpointUrl().get().toString()).isEqualTo(url); + } + + @Test + public void parsesOauthClientId() { + String client_id = "client_id"; + CommandLineArgs params = parseArgs(ImmutableList.of( + String.format("--oauth_client_id=%s",client_id))); + assertThat(params.oauthClientId().get()).isEqualTo(client_id); + } + + @Test + public void parsesOauthClientSecret() { + String client_secret = "client_secret"; + CommandLineArgs params = parseArgs(ImmutableList.of( + String.format("--oauth_client_secret=%s", client_secret))); + assertThat(params.oauthClientSecret().get()).isEqualTo(client_secret); + } + + @Test + public void parsesOauthRefreshTokenPath() { + CommandLineArgs params = parseArgs(ImmutableList.of( + String.format("--oauth_refresh_token_path=%s", tempFile1.toString()))); + assertThat(params.oauthRefreshTokenPath().get().toString()).isEqualTo(tempFile1.toString()); + } + + @Test + public void parsesOauthAccessTokenPath() { + CommandLineArgs params = parseArgs(ImmutableList.of( + String.format("--oauth_access_token_path=%s", tempFile1.toString()))); + assertThat(params.oauthAccessTokenPath().get().toString()).isEqualTo(tempFile1.toString()); + } + private static CommandLineArgs parseArgs(ImmutableList args) { ImmutableList allArgs = ImmutableList.builder() - .addAll(args) - .add("--endpoint=somehost:1234") - .add("--full_method=some.package/Method") - .add("--proto_discovery_root=.") - .build(); + .addAll(args) + .add("--endpoint=somehost:1234") + .add("--full_method=some.package/Method") + .add("--proto_discovery_root=.") + .build(); return CommandLineArgs.parse(allArgs.toArray(new String[0])); } } diff --git a/src/test/java/me/dinowernli/grpc/polyglot/config/ConfigurationLoaderTest.java b/src/test/java/me/dinowernli/grpc/polyglot/config/ConfigurationLoaderTest.java index 3d269b5..6b272a4 100644 --- a/src/test/java/me/dinowernli/grpc/polyglot/config/ConfigurationLoaderTest.java +++ b/src/test/java/me/dinowernli/grpc/polyglot/config/ConfigurationLoaderTest.java @@ -1,7 +1,9 @@ package me.dinowernli.grpc.polyglot.config; +import java.net.MalformedURLException; import java.nio.file.Paths; import java.util.Optional; +import java.net.URL; import com.google.common.collect.ImmutableList; import me.dinowernli.junit.TestClass; @@ -18,6 +20,8 @@ import polyglot.ConfigProto.ConfigurationSet; import polyglot.ConfigProto.OutputConfiguration.Destination; +import javax.swing.text.html.Option; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.when; @@ -44,7 +48,7 @@ public void tearDown() { @Test public void loadsDefaultConfig() { Configuration defaultConfig = - ConfigurationLoader.forDefaultConfigSet().getDefaultConfiguration(); + ConfigurationLoader.forDefaultConfigSet().getDefaultConfiguration(); assertThat(defaultConfig).isEqualTo(Configuration.getDefaultInstance()); assertThat(defaultConfig.getCallConfig().getUseTls()).isFalse(); @@ -59,20 +63,20 @@ public void throwsIfAskedToLoadNamedFromDefaultSet() { @Test(expected = IllegalArgumentException.class) public void throwsIfNamedConfigMissing() { ConfigurationLoader.forConfigSet(ConfigurationSet.getDefaultInstance()) - .getNamedConfiguration("asfd"); + .getNamedConfiguration("asfd"); } @Test public void loadsNamedConfig() { ConfigurationLoader loader = ConfigurationLoader.forConfigSet(ConfigurationSet.newBuilder() - .addConfigurations(namedConfig("foo")) - .addConfigurations(namedConfig("bar")) - .build()); + .addConfigurations(namedConfig("foo")) + .addConfigurations(namedConfig("bar")) + .build()); assertThat(loader.getNamedConfiguration("foo").getName()).isEqualTo("foo"); } @Test - public void appliesOverrides() { + public void appliesOverridesWithRefreshToken() { when(mockOverrides.useTls()).thenReturn(Optional.of(true)); when(mockOverrides.outputFilePath()).thenReturn(Optional.of(Paths.get("asdf"))); when(mockOverrides.additionalProtocIncludes()).thenReturn(ImmutableList.of(Paths.get("."))); @@ -82,11 +86,16 @@ public void appliesOverrides() { when(mockOverrides.tlsClientCertPath()).thenReturn(Optional.of(Paths.get("client_cert"))); when(mockOverrides.tlsClientKeyPath()).thenReturn(Optional.of(Paths.get("client_key"))); when(mockOverrides.tlsClientOverrideAuthority()).thenReturn(Optional.of("override_authority")); + when(mockOverrides.oauthRefreshTokenEndpointUrl()).thenReturn(Optional.of(getTestUrl("https://github.com/grpc-ecosystem/polyglot"))); + when(mockOverrides.oauthClientId()).thenReturn(Optional.of("id")); + when(mockOverrides.oauthClientSecret()).thenReturn(Optional.of("secret")); + when(mockOverrides.oauthRefreshTokenPath()).thenReturn(Optional.of(Paths.get("asdf"))); + when(mockOverrides.oauthAccessTokenPath()).thenReturn(Optional.empty()); Configuration config = ConfigurationLoader - .forDefaultConfigSet() - .withOverrides(mockOverrides) - .getDefaultConfiguration(); + .forDefaultConfigSet() + .withOverrides(mockOverrides) + .getDefaultConfiguration(); assertThat(config.getOutputConfig().getDestination()).isEqualTo(Destination.FILE); @@ -97,11 +106,64 @@ public void appliesOverrides() { assertThat(callConfig.getTlsClientCertPath()).isEqualTo("client_cert"); assertThat(callConfig.getTlsClientKeyPath()).isEqualTo("client_key"); assertThat(callConfig.getTlsClientOverrideAuthority()).isEqualTo("override_authority"); + assertThat(callConfig.getDeadlineMs()).isEqualTo(25); + assertThat(callConfig.getOauthConfig().getRefreshTokenCredentials().getTokenEndpointUrl()) + .isEqualTo("https://github.com/grpc-ecosystem/polyglot"); + assertThat(callConfig.getOauthConfig().getRefreshTokenCredentials().getClient().getId()).isEqualTo("id"); + assertThat(callConfig.getOauthConfig().getRefreshTokenCredentials().getClient().getSecret()).isEqualTo("secret"); + assertThat(callConfig.getOauthConfig().getRefreshTokenCredentials().getRefreshTokenPath()).isNotEmpty(); + assertThat(callConfig.getOauthConfig().getAccessTokenCredentials().getAccessTokenPath()).isEmpty(); + } + + @Test + public void appliesOverridesWithAccessToken() { + when(mockOverrides.useTls()).thenReturn(Optional.of(true)); + when(mockOverrides.outputFilePath()).thenReturn(Optional.of(Paths.get("asdf"))); + when(mockOverrides.additionalProtocIncludes()).thenReturn(ImmutableList.of(Paths.get("."))); + when(mockOverrides.protoDiscoveryRoot()).thenReturn(Optional.of(Paths.get("."))); + when(mockOverrides.getRpcDeadlineMs()).thenReturn(Optional.of(25)); + when(mockOverrides.tlsCaCertPath()).thenReturn(Optional.of(Paths.get("asdf"))); + when(mockOverrides.tlsClientCertPath()).thenReturn(Optional.of(Paths.get("client_cert"))); + when(mockOverrides.tlsClientKeyPath()).thenReturn(Optional.of(Paths.get("client_key"))); + when(mockOverrides.tlsClientOverrideAuthority()).thenReturn(Optional.of("override_authority")); + when(mockOverrides.oauthRefreshTokenEndpointUrl()).thenReturn(Optional.of(getTestUrl("https://github.com/grpc-ecosystem/polyglot"))); + when(mockOverrides.oauthClientId()).thenReturn(Optional.of("id")); + when(mockOverrides.oauthClientSecret()).thenReturn(Optional.of("secret")); + when(mockOverrides.oauthRefreshTokenPath()).thenReturn(Optional.of(Paths.get("asdf"))); + when(mockOverrides.oauthAccessTokenPath()).thenReturn(Optional.of(Paths.get("asdf"))); + + Configuration config = ConfigurationLoader + .forDefaultConfigSet() + .withOverrides(mockOverrides) + .getDefaultConfiguration(); + + CallConfiguration callConfig = config.getCallConfig(); + assertThat(callConfig.getUseTls()).isTrue(); + assertThat(config.getOutputConfig().getDestination()).isEqualTo(Destination.FILE); + assertThat(callConfig.getDeadlineMs()).isEqualTo(25); + assertThat(callConfig.getTlsCaCertPath()).isNotEmpty(); + assertThat(callConfig.getTlsClientCertPath()).isEqualTo("client_cert"); + assertThat(callConfig.getTlsClientKeyPath()).isEqualTo("client_key"); + assertThat(callConfig.getTlsClientOverrideAuthority()).isEqualTo("override_authority"); + assertThat(callConfig.getDeadlineMs()).isEqualTo(25); + // Setting the access token path will unset all of the refresh token properties (due to the oneof semantics) + assertThat(callConfig.getOauthConfig().getRefreshTokenCredentials().getTokenEndpointUrl()).isEmpty(); + assertThat(callConfig.getOauthConfig().getRefreshTokenCredentials().getClient().getId()).isEmpty(); + assertThat(callConfig.getOauthConfig().getRefreshTokenCredentials().getClient().getSecret()).isEmpty(); + assertThat(callConfig.getOauthConfig().getRefreshTokenCredentials().getRefreshTokenPath()).isEmpty(); + assertThat(callConfig.getOauthConfig().getAccessTokenCredentials().getAccessTokenPath()).isNotEmpty(); } private static Configuration namedConfig(String name) { return Configuration.newBuilder() - .setName(name) - .build(); + .setName(name) + .build(); + } + private static URL getTestUrl(String testUrl) { + try { + return new URL(testUrl); + } catch (MalformedURLException mUrlE) { + throw new RuntimeException(); + } } } diff --git a/src/test/java/me/dinowernli/grpc/polyglot/io/MessageWriterTest.java b/src/test/java/me/dinowernli/grpc/polyglot/io/MessageWriterTest.java index 0fd66ce..251ca8f 100644 --- a/src/test/java/me/dinowernli/grpc/polyglot/io/MessageWriterTest.java +++ b/src/test/java/me/dinowernli/grpc/polyglot/io/MessageWriterTest.java @@ -28,7 +28,7 @@ public class MessageWriterTest { @Before public void setUp() { recordingOutput = new RecordingOutput(); - writer = new MessageWriter<>(JsonFormat.printer(), recordingOutput); + writer = new MessageWriter(JsonFormat.printer(), recordingOutput); } @Test diff --git a/third_party/protobuf/BUILD b/third_party/protobuf/BUILD index 2687a04..36e88a4 100644 --- a/third_party/protobuf/BUILD +++ b/third_party/protobuf/BUILD @@ -1,8 +1,10 @@ +load("@org_pubref_rules_protobuf//java:rules.bzl", "java_proto_library") + +licenses(["permissive"]) package(default_visibility = ["//visibility:public"]) java_library( name = "protobuf", - licenses = ["permissive"], exports = [ "@com_google_code_gson//jar", "@protobuf_java_artifact//jar", diff --git a/third_party/testing/BUILD b/third_party/testing/BUILD index aaad010..ac160ff 100644 --- a/third_party/testing/BUILD +++ b/third_party/testing/BUILD @@ -1,3 +1,4 @@ + package(default_visibility = ["//visibility:public"]) java_library(