Skip to content

Commit

Permalink
Merge pull request #14309 from radcortez/config-console
Browse files Browse the repository at this point in the history
Improve Config Console
  • Loading branch information
stuartwdouglas authored Feb 11, 2021
2 parents 071a720 + e95d5cb commit 57eaa7d
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 133 deletions.
Original file line number Diff line number Diff line change
@@ -1,171 +1,96 @@
package io.quarkus.vertx.http.deployment.devmode.console;

import static io.quarkus.runtime.LaunchMode.DEVELOPMENT;

import java.io.BufferedWriter;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;

import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;

import io.quarkus.arc.runtime.ConfigRecorder;
import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.ConfigDescriptionBuildItem;
import io.quarkus.dev.console.DevConsoleManager;
import io.quarkus.devconsole.runtime.spi.DevConsolePostHandler;
import io.quarkus.devconsole.spi.DevConsoleRouteBuildItem;
import io.quarkus.devconsole.spi.DevConsoleTemplateInfoBuildItem;
import io.quarkus.devconsole.spi.DevConsoleRuntimeTemplateInfoBuildItem;
import io.quarkus.runtime.configuration.ProfileManager;
import io.quarkus.vertx.http.runtime.devmode.ConfigDescription;
import io.quarkus.vertx.http.runtime.devmode.ConfigDescriptionsSupplier;
import io.vertx.core.MultiMap;
import io.vertx.ext.web.RoutingContext;

public class ConfigEditorProcessor {

@BuildStep
DevConsoleTemplateInfoBuildItem config(List<ConfigDescriptionBuildItem> config) throws Exception {
List<CurrentConfig> configs = new ArrayList<>();
Config current = ConfigProvider.getConfig();

Properties appProperties = new Properties();
Path appProps = null;
for (Path i : DevConsoleManager.getHotReplacementContext().getResourcesDir()) {
Path app = i.resolve("application.properties");
if (Files.exists(app)) {
appProps = app;
break;
}
}
if (appProps != null) {
try (InputStream in = Files.newInputStream(appProps)) {
appProperties.load(in);
}
}

for (ConfigDescriptionBuildItem i : config) {
if (i.getPropertyName().contains("*")) {
continue; //TODO: complex properties
}
configs.add(new CurrentConfig(i.getPropertyName(), i.getDocs(), i.getDefaultValue(),
current.getOptionalValue(i.getPropertyName(), String.class).orElse(null),
appProperties.getProperty(i.getPropertyName())));
@BuildStep(onlyIf = IsDevelopment.class)
@Record(ExecutionTime.RUNTIME_INIT)
public DevConsoleRuntimeTemplateInfoBuildItem config(ConfigRecorder recorder,
List<ConfigDescriptionBuildItem> configDescriptionBuildItems) {
List<ConfigDescription> configDescriptions = new ArrayList<>();
for (ConfigDescriptionBuildItem item : configDescriptionBuildItems) {
configDescriptions.add(
new ConfigDescription(item.getPropertyName(), item.getDocs(), item.getDefaultValue()));
}
Collections.sort(configs);

return new DevConsoleTemplateInfoBuildItem("config", configs);
return new DevConsoleRuntimeTemplateInfoBuildItem("config", new ConfigDescriptionsSupplier(configDescriptions));
}

@BuildStep
DevConsoleRouteBuildItem handlePost() {
return new DevConsoleRouteBuildItem("config", "POST", new DevConsolePostHandler() {
@Override
protected void handlePost(RoutingContext event, MultiMap form) throws Exception {
String key = event.request().getFormAttribute("name");
String name = event.request().getFormAttribute("name");
String value = event.request().getFormAttribute("value");

Properties appProperties = new Properties();
Path appProps = null;
for (Path i : DevConsoleManager.getHotReplacementContext().getResourcesDir()) {
Path app = i.resolve("application.properties");
if (Files.exists(app)) {
appProps = app;
List<Path> resourcesDir = DevConsoleManager.getHotReplacementContext().getResourcesDir();
if (resourcesDir.isEmpty()) {
throw new IllegalStateException("Unable to manage configurations - no resource directory found");
}

// In the current project only
Path path = resourcesDir.get(0);
Path configPath = path.resolve("application.properties");
if (!Files.exists(configPath)) {
configPath = Files.createFile(path.resolve("application.properties"));
}

String profile = ProfileManager.getActiveProfile();
name = !profile.equals(DEVELOPMENT.getDefaultProfile()) ? "%" + profile + "." + name : name;
List<String> lines = Files.readAllLines(configPath);
int nameLine = -1;
for (int i = 0, linesSize = lines.size(); i < linesSize; i++) {
final String line = lines.get(i);
if (line.startsWith(name + "=")) {
nameLine = i;
break;
}
}
boolean present = false;
if (appProps != null) {
try (InputStream in = Files.newInputStream(appProps)) {
appProperties.load(in);
present = appProperties.containsKey(key);

if (nameLine != -1) {
if (value.isEmpty()) {
lines.remove(nameLine);
} else {
lines.set(nameLine, name + "=" + value);
}
} else {
// If there is no application.properties file then create a new one in the first module
List<Path> resourcesDir = DevConsoleManager.getHotReplacementContext().getResourcesDir();
if (resourcesDir.isEmpty()) {
throw new IllegalStateException(
"Unable to create application.properties - no resource directory found");
if (!value.isEmpty()) {
lines.add(name + "=" + value);
}
appProps = Files.createFile(resourcesDir.get(0).resolve("application.properties"));
}
if (!present) {
try (OutputStream out = Files.newOutputStream(appProps, StandardOpenOption.APPEND)) {
out.write(("\n" + key + "=" + value).getBytes(StandardCharsets.UTF_8)); //TODO: escpaing
}
} else {
List<String> lines = Files.readAllLines(appProps);
Iterator<String> it = lines.iterator();
while (it.hasNext()) {
String val = it.next();
if (val.startsWith(key + "=")) {
it.remove();
}
}
lines.add(key + "=" + value);
try (BufferedWriter writer = Files.newBufferedWriter(appProps)) {
for (String i : lines) {
writer.write(i);
writer.newLine();
}

try (BufferedWriter writer = Files.newBufferedWriter(configPath)) {
for (String i : lines) {
writer.write(i);
writer.newLine();
}
}

DevConsoleManager.getHotReplacementContext().doScan(true);
flashMessage(event, "Configuration updated");
}
});
}

public static class CurrentConfig implements Comparable<CurrentConfig> {
private final String propertyName;
private final String description;
private final String defaultValue;
private final String currentValue;
private final String appPropertiesValue;

public CurrentConfig(String propertyName, String description, String defaultValue, String currentValue,
String appPropertiesValue) {
this.propertyName = propertyName;
this.description = description;
this.defaultValue = defaultValue;
this.currentValue = currentValue;
this.appPropertiesValue = appPropertiesValue;
}

public String getPropertyName() {
return propertyName;
}

public String getDescription() {
return description;
}

public String getDefaultValue() {
return defaultValue;
}

public String getCurrentValue() {
return currentValue;
}

public String getAppPropertiesValue() {
return appPropertiesValue;
}

@Override
public int compareTo(CurrentConfig o) {
if (appPropertiesValue == null && o.appPropertiesValue != null) {
return 1;
}
if (appPropertiesValue != null && o.appPropertiesValue == null) {
return -1;
}

return propertyName.compareTo(o.propertyName);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,30 @@
<th scope="col">Value</th>
<th scope="col">Update</th>
<th scope="col">Default Value</th>
<th scope="col">Source</th>
<th scope="col">Description</th>
</tr>
</thead>
<tbody>
{#for item in info:config}
<tr class="{item.appPropertiesValue ? 'table-info' : ''}">
<tr>
<form method="post" enctype="application/x-www-form-urlencoded">
<input type="hidden" name="name" value="{item.propertyName}"/>
<input type="hidden" name="name" value="{item.configValue.name}"/>
<td>
{item.propertyName}
{item.configValue.name}
</td>
<td>
<input type="text" name="value" value="{item.currentValue}"/>
<input type="text" name="value" value="{item.configValue.value}"/>
</td>
<td>
<input type="submit" value="Update" >
</td>
<td>
{item.defaultValue}
</td>
<td>
{item.configValue.configSourceName}
</td>
<td>
{item.description.fmtJavadoc}
</td>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,20 @@ public void testChangeHttpRoute() {

}

@Test
public void testSetEmptyValue() {
RestAssured.with()
.get("q/arc/beans")
.then()
.statusCode(200);
RestAssured.with().formParam("name", "quarkus.http.root-path").formParam("value", "")
.redirects().follow(false)
.post("q/dev/io.quarkus.quarkus-vertx-http/config")
.then()
.statusCode(303);
RestAssured.with()
.get("q/arc/beans")
.then()
.statusCode(200);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package io.quarkus.vertx.http.runtime.devmode;

import io.smallrye.config.ConfigValue;

public class ConfigDescription implements Comparable<ConfigDescription> {
private String name;
private String description;
private String defaultValue;
private ConfigValue configValue;

public ConfigDescription() {
}

public ConfigDescription(final String name, final String description, final String defaultValue) {
this.name = name;
this.description = description;
this.defaultValue = defaultValue;
}

public ConfigDescription(
final String name,
final String description,
final String defaultValue,
final ConfigValue configValue) {
this.name = name;
this.description = description;
this.defaultValue = defaultValue;
this.configValue = configValue;
}

public String getName() {
return name;
}

public void setName(final String name) {
this.name = name;
}

public String getDescription() {
return description;
}

public void setDescription(final String description) {
this.description = description;
}

public String getDefaultValue() {
return defaultValue;
}

public void setDefaultValue(final String defaultValue) {
this.defaultValue = defaultValue;
}

public ConfigValue getConfigValue() {
return configValue;
}

public void setConfigValue(final ConfigValue configValue) {
this.configValue = configValue;
}

@Override
public int compareTo(ConfigDescription o) {
int ordinal = Integer.compare(o.configValue.getConfigSourceOrdinal(), this.configValue.getConfigSourceOrdinal());
if (ordinal == 0) {
return this.configValue.getName().compareTo(o.configValue.getName());
}
return ordinal;
}
}
Loading

0 comments on commit 57eaa7d

Please sign in to comment.