Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Config viewer as part of vertx-http in dev mode #7430

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 5 additions & 0 deletions bom/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2242,6 +2242,11 @@
<artifactId>smallrye-mutiny-vertx-web-client</artifactId>
<version>${mutiny-client.version}</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
<version>${vertx.version}</version>
</dependency>
Copy link
Member

Choose a reason for hiding this comment

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

This dependency is already defined in the vert.x bom. Isn't it?

<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@
import java.util.Optional;
import java.util.stream.Collectors;

import javax.inject.Singleton;

import org.eclipse.microprofile.config.ConfigProvider;
import org.jboss.jandex.DotName;

import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.BeanContainerBuildItem;
import io.quarkus.builder.BuildException;
import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.IsNormal;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
Expand All @@ -30,11 +34,14 @@
import io.quarkus.vertx.core.deployment.EventLoopCountBuildItem;
import io.quarkus.vertx.core.deployment.InternalWebVertxBuildItem;
import io.quarkus.vertx.http.runtime.CurrentVertxRequest;
import io.quarkus.vertx.http.runtime.HandlerType;
import io.quarkus.vertx.http.runtime.HttpBuildTimeConfig;
import io.quarkus.vertx.http.runtime.HttpConfiguration;
import io.quarkus.vertx.http.runtime.RouterProducer;
import io.quarkus.vertx.http.runtime.VertxHttpRecorder;
import io.quarkus.vertx.http.runtime.cors.CORSRecorder;
import io.quarkus.vertx.http.runtime.devmode.ConfigHolder;
import io.quarkus.vertx.http.runtime.devmode.ConfigViewerHandler;
import io.quarkus.vertx.http.runtime.filters.Filter;
import io.vertx.core.Handler;
import io.vertx.core.impl.VertxImpl;
Expand Down Expand Up @@ -166,4 +173,17 @@ ServiceStartBuildItem finalizeRouter(
RuntimeInitializedClassBuildItem configureNativeCompilation() {
return new RuntimeInitializedClassBuildItem("io.vertx.ext.web.handler.sockjs.impl.XhrTransport");
}

@BuildStep(onlyIf = IsDevelopment.class)
public void configViewerRoute(HttpBuildTimeConfig httpBuildTimeConfig,
BuildProducer<AdditionalBeanBuildItem> beans,
BuildProducer<RouteBuildItem> routes) {
beans.produce(AdditionalBeanBuildItem.builder()
.addBeanClass(ConfigHolder.class)
.setDefaultScope(DotName.createSimple(Singleton.class.getName()))
.setUnremovable()
.build());
routes.produce(new RouteBuildItem(httpBuildTimeConfig.configPath,
new ConfigViewerHandler(), HandlerType.BLOCKING));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.quarkus.vertx.http.configviewer;

import static io.restassured.RestAssured.when;
import static org.hamcrest.Matchers.hasItem;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusDevModeTest;
import io.restassured.http.ContentType;

public class ShowConfigTest {

@RegisterExtension
static final QuarkusDevModeTest test = new QuarkusDevModeTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addAsResource(new StringAsset("foo=bar"), "application.properties"));

@Test
void testConfig() {
when().get("/config").then()
.statusCode(200)
.contentType(ContentType.JSON)
.body("sources.properties.foo", hasItem("bar"));
}
}
4 changes: 4 additions & 0 deletions extensions/vertx-http/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this needed? Isn't the io.vertx:vertx-web dependency added above enough?

Copy link
Author

Choose a reason for hiding this comment

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

I've tried w/o, but got a `java.lang.ClassNotFoundException:

[INFO] Running io.quarkus.vertx.http.configviewer.ShowConfigTest
2020-02-26 10:57:52,162 INFO  [io.quarkus] (main) Quarkus 999-SNAPSHOT started in 1.513s. Listening on: http://0.0.0.0:8080
2020-02-26 10:57:52,162 INFO  [io.quarkus] (main) Profile dev activated. Live Coding activated.
2020-02-26 10:57:52,162 INFO  [io.quarkus] (main) Installed features: [cdi, security]
2020-02-26 10:57:53,319 ERROR [io.qua.ver.htt.run.QuarkusErrorHandler] (vert.x-eventloop-thread-4) HTTP Request to /config failed, error id: f23efab7-ebff-4242-b10e-c4efa772f718-1: java.lang.NoClassDefFoundError: com/fasterxml/jackson/databind/JsonSerializer
	at io.vertx.core.json.jackson.JacksonCodec.toString(JacksonCodec.java:151)
	at io.vertx.core.json.JsonObject.encode(JsonObject.java:785)
	at io.quarkus.vertx.http.runtime.devmode.ConfigViewerHandler.handle(ConfigViewerHandler.java:19)
	at io.quarkus.vertx.http.runtime.devmode.ConfigViewerHandler.handle(ConfigViewerHandler.java:11)
	at io.vertx.ext.web.impl.BlockingHandlerDecorator.lambda$handle$0(BlockingHandlerDecorator.java:48)
	at io.vertx.core.impl.ContextImpl.lambda$executeBlocking$2(ContextImpl.java:316)
	at io.vertx.core.impl.TaskQueue.run(TaskQueue.java:76)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.ClassNotFoundException: com.fasterxml.jackson.databind.JsonSerializer
	at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:291)
	at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:251)
	... 11 more

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah yes, that is because we exclude Jackson databind - and we need to continue doing this, we can't let it sneak in.
Can the JSON part you PR just be based on Jackson core instead of relying on databind?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, using JSON Object "encode" requires Jackson, which we want to avoid in vertx-http. It may require a bit of code, but in your case, Jackson should not be required if you just implement a simple "encode" method doing the ToJson transformation manually. As you know the structure of the result, you can just generate the JSON string.

Copy link
Author

Choose a reason for hiding this comment

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

Ok, I'm going to remove the dependency to Jackson and do the JSON encoding manually.

<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,10 @@ public class HttpBuildTimeConfig {
*/
@ConfigItem
public boolean virtual;

/**
* The path for the config viewer.
*/
@ConfigItem(defaultValue = "/config")
public String configPath;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.quarkus.vertx.http.runtime.devmode;

import javax.inject.Inject;

import org.eclipse.microprofile.config.Config;

public class ConfigHolder {

@Inject
Config config;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package io.quarkus.vertx.http.runtime.devmode;

import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.spi.ConfigSource;
import org.jboss.logging.Logger;

import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;

/**
* Turns a {@link Config} into a JSON object with all config sources and properties as JSON. The config sources are
* sorted descending by ordinal, the properties by name. If no config is defined an empty JSON object is returned.
*
* <p>
* A typical output might look like:
* </p>
*
* <pre>
* {
* "sources": [
* {
* "source": "source0",
* "ordinal": 200,
* "properties": {
* "key": "value"
* }
* },
* {
* "source": "source1",
* "ordinal": 100,
* "properties": {
* "key": "value"
* }
* }
* ]
* }
* </pre>
*/
class ConfigViewer {

private static final Logger LOGGER = Logger.getLogger(ConfigViewer.class.getName());

JsonObject dump(Config config) {
JsonObject json = new JsonObject();
if (config != null) {
if (config.getConfigSources().iterator().hasNext()) {
JsonArray jsonSources = new JsonArray();
for (ConfigSource source : config.getConfigSources()) {
JsonObject jsonSource = new JsonObject();
jsonSource.put("source", source.getName())
.put("ordinal", source.getOrdinal());
Set<String> propertyNames = source.getPropertyNames();
if (!propertyNames.isEmpty()) {
SortedSet<String> sortedPropertyNames = new TreeSet<>(propertyNames);
JsonObject jsonProperties = new JsonObject();
for (String propertyName : sortedPropertyNames) {
try {
jsonProperties.put(propertyName, source.getValue(propertyName));
Copy link
Member

Choose a reason for hiding this comment

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

It looks like this is going to print every config value from every config source (as opposed to printing only the effective values). I assume that this was your intent!

One concern I have is that this will expand property expressions. If you don't want that (and I suspect you don't since you're printing all the raw config keys rather than just the effective configuration contents), then the whole thing should be enclosed like this:

boolean old = ExpandingConfigSource.setExpanding(false);
try {
    // ... do all the work here ...
} finally {
    ExpandingConfigSource.setExpanding(old);
}

Copy link
Member

Choose a reason for hiding this comment

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

Another concern I have is that this might expose secrets/passwords!

Copy link
Author

Choose a reason for hiding this comment

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

Thanks @dmlloyd
Right, I only want to print the effective values. Will wrap the code like you suggested.

I don't have a solution how to hide secrets / passwords.

Copy link
Member

Choose a reason for hiding this comment

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

Let me post something to the dev list on this. I have an idea... I think.

Copy link
Member

Choose a reason for hiding this comment

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

Actually I'll post an issue for it...

Copy link
Member

Choose a reason for hiding this comment

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

I've opened #7442 for this.

} catch (Throwable t) {
LOGGER.errorf("Cannot get configuration value for '%s': %s",
propertyName, t.getMessage());
}
}
jsonSource.put("properties", jsonProperties);
}
jsonSources.add(jsonSource);
}
json.put("sources", jsonSources);
}
}
return json;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.quarkus.vertx.http.runtime.devmode;

import javax.enterprise.inject.spi.CDI;

import io.vertx.core.Handler;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;

public class ConfigViewerHandler implements Handler<RoutingContext> {

@Override
public void handle(RoutingContext routingContext) {
ConfigHolder configHolder = CDI.current().select(ConfigHolder.class).get();
JsonObject json = new ConfigViewer().dump(configHolder.config);
HttpServerResponse resp = routingContext.response();
resp.putHeader("content-type", "application/json");
resp.end(Buffer.buffer(json.encode()));
}
}