Commit bcab23e5 authored by Stephane Nicoll's avatar Stephane Nicoll

Polish "Separate endpoint concerns"

* Fix the endpoint prefix for generated metadata.
* Polish and improve configuration key descriptions.

Closes gh-10176
parent 9411d176
...@@ -34,17 +34,12 @@ import org.springframework.util.StringUtils; ...@@ -34,17 +34,12 @@ import org.springframework.util.StringUtils;
public class JmxEndpointProperties { public class JmxEndpointProperties {
/** /**
* Whether JMX endpoints are enabled. * Endpoint IDs that should be exposed or '*' for all.
*/
private boolean enabled;
/**
* The IDs of endpoints that should be exposed or '*' for all.
*/ */
private Set<String> expose = new LinkedHashSet<>(); private Set<String> expose = new LinkedHashSet<>();
/** /**
* The IDs of endpoints that should be excluded. * Endpoint IDs that should be excluded.
*/ */
private Set<String> exclude = new LinkedHashSet<>(); private Set<String> exclude = new LinkedHashSet<>();
...@@ -71,14 +66,6 @@ public class JmxEndpointProperties { ...@@ -71,14 +66,6 @@ public class JmxEndpointProperties {
} }
} }
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public Set<String> getExpose() { public Set<String> getExpose() {
return this.expose; return this.expose;
} }
......
...@@ -34,38 +34,25 @@ import org.springframework.boot.context.properties.ConfigurationProperties; ...@@ -34,38 +34,25 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
public class WebEndpointProperties { public class WebEndpointProperties {
/** /**
* Whether web endpoints are enabled. * Base path for Web endpoints. Relative to server.context-path or
*/ * management.server.context-path if management.server.port is configured.
private boolean enabled;
/**
* The base-path for the web endpoints. Relative to `server.context-path` or
* `management.server.context-path`, if `management.server.port` is different.
*/ */
private String basePath = "/application"; private String basePath = "/application";
/** /**
* The IDs of endpoints that should be exposed or '*' for all. * Endpoint IDs that should be exposed or '*' for all.
*/ */
private Set<String> expose = new LinkedHashSet<>(); private Set<String> expose = new LinkedHashSet<>();
/** /**
* The IDs of endpoints that should be excluded. * Endpoint IDs that should be excluded.
*/ */
private Set<String> exclude = new LinkedHashSet<>(); private Set<String> exclude = new LinkedHashSet<>();
/** /**
* Mapping between endpoint IDs and the path that should expose them. * Mapping between endpoint IDs and the path that should expose them.
*/ */
private Map<String, String> pathMapping = new LinkedHashMap<>(); private final Map<String, String> pathMapping = new LinkedHashMap<>();
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getBasePath() { public String getBasePath() {
return this.basePath; return this.basePath;
...@@ -95,8 +82,4 @@ public class WebEndpointProperties { ...@@ -95,8 +82,4 @@ public class WebEndpointProperties {
return this.pathMapping; return this.pathMapping;
} }
public void setPathMapping(Map<String, String> pathMapping) {
this.pathMapping = pathMapping;
}
} }
...@@ -10,11 +10,6 @@ ...@@ -10,11 +10,6 @@
"vcap_services" "vcap_services"
] ]
}, },
{
"name": "management.endpoints.enabled-by-default",
"type": "java.lang.Boolean",
"description": "Enable or disable all endpoints by default."
},
{ {
"name": "management.endpoint.env.keys-to-sanitize", "name": "management.endpoint.env.keys-to-sanitize",
"defaultValue": [ "defaultValue": [
...@@ -36,6 +31,34 @@ ...@@ -36,6 +31,34 @@
"level": "error" "level": "error"
} }
}, },
{
"name": "management.endpoints.enabled-by-default",
"type": "java.lang.Boolean",
"description": "Enable or disable all endpoints by default."
},
{
"name": "management.endpoints.jmx.enabled",
"type": "java.lang.Boolean",
"description": "Whether JMX endpoints are enabled.",
"defaultValue": true
},
{
"name": "management.endpoints.jmx.expose",
"defaultValue": "*"
},
{
"name": "management.endpoints.web.enabled",
"type": "java.lang.Boolean",
"description": "Whether web endpoints are enabled.",
"defaultValue": true
},
{
"name": "management.endpoints.web.expose",
"defaultValue": [
"info",
"status"
]
},
{ {
"name": "info", "name": "info",
"type": "java.util.Map<java.lang.String,java.lang.Object>", "type": "java.util.Map<java.lang.String,java.lang.Object>",
...@@ -1188,6 +1211,79 @@ ...@@ -1188,6 +1211,79 @@
} }
} }
],"hints": [ ],"hints": [
{
"name": "management.endpoints.web.path-mapping.keys",
"values": [
{
"value": "auditevents"
},
{
"value": "beans"
},
{
"value": "conditions"
},
{
"value": "configprops"
},
{
"value": "env"
},
{
"value": "flyway"
},
{
"value": "health"
},
{
"value": "heapdump"
},
{
"value": "info"
},
{
"value": "liquibase"
},
{
"value": "logfile"
},
{
"value": "loggers"
},
{
"value": "mappings"
},
{
"value": "metrics"
},
{
"value": "prometheus"
},
{
"value": "scheduledtasks"
},
{
"value": "sessions"
},
{
"value": "shutdown"
},
{
"value": "status"
},
{
"value": "threaddump"
},
{
"value": "trace"
}
],
"providers": [
{
"name": "any"
}
]
},
{ {
"name": "management.endpoints.web.cors.allowed-headers", "name": "management.endpoints.web.cors.allowed-headers",
"values": [ "values": [
......
...@@ -1096,20 +1096,23 @@ content into your application; rather pick only the properties that you need. ...@@ -1096,20 +1096,23 @@ content into your application; rather pick only the properties that you need.
management.cloudfoundry.enabled=true # Enable extended Cloud Foundry actuator endpoints. management.cloudfoundry.enabled=true # Enable extended Cloud Foundry actuator endpoints.
management.cloudfoundry.skip-ssl-validation=false # Skip SSL verification for Cloud Foundry actuator endpoint security calls. management.cloudfoundry.skip-ssl-validation=false # Skip SSL verification for Cloud Foundry actuator endpoint security calls.
# ENDPOINTS JMX CONFIGURATION ({sc-spring-boot-actuator-autoconfigure}/endpoint/jmx/JmxEndpointExporterProperties.{sc-ext}[JmxEndpointExporterProperties]) # ENDPOINTS GENERAL CONFIGURATION
management.endpoints.jmx.enabled= # Whether JMX endpoints are enabled management.endpoints.enabled-by-default= # Enable or disable all endpoints by default.
management.endpoints.jmx.expose= The IDs of endpoints to expose or '*' for all (default is 'info', 'status')
management.endpoints.jmx.exclude= The IDs of endpoints to exclude # ENDPOINTS JMX CONFIGURATION ({sc-spring-boot-actuator-autoconfigure}/endpoint/jmx/JmxEndpointProperties.{sc-ext}[JmxEndpointProperties])
management.endpoints.jmx.enabled=true # Whether JMX endpoints are enabled.
management.endpoints.jmx.expose=* # Endpoint IDs that should be exposed or '*' for all.
management.endpoints.jmx.exclude= # Endpoint IDs that should be excluded.
management.endpoints.jmx.domain=org.springframework.boot # Endpoints JMX domain name. Fallback to 'spring.jmx.default-domain' if set. management.endpoints.jmx.domain=org.springframework.boot # Endpoints JMX domain name. Fallback to 'spring.jmx.default-domain' if set.
management.endpoints.jmx.static-names=false # Additional static properties to append to all ObjectNames of MBeans representing Endpoints. management.endpoints.jmx.static-names=false # Additional static properties to append to all ObjectNames of MBeans representing Endpoints.
management.endpoints.jmx.unique-names=false # Ensure that ObjectNames are modified in case of conflict. management.endpoints.jmx.unique-names=false # Ensure that ObjectNames are modified in case of conflict.
# ENDPOINTS WEB CONFIGURATION ({sc-spring-boot-actuator-autoconfigure}/endpoint/web/WebEndpointProperties.{sc-ext}[WebEndpointProperties]) # ENDPOINTS WEB CONFIGURATION ({sc-spring-boot-actuator-autoconfigure}/endpoint/web/WebEndpointProperties.{sc-ext}[WebEndpointProperties])
management.endpoints.web.enabled= # Whether web endpoints are enabled management.endpoints.web.enabled=true # Whether web endpoints are enabled
management.endpoints.web.expose= The IDs of endpoints to expose or '*' for all (default is '*') management.endpoints.web.expose=info,status # Endpoint IDs that should be exposed or '*' for all.
management.endpoints.web.exclude= The IDs of endpoints to exclude management.endpoints.web.exclude= # Endpoint IDs that should be excluded.
management.endpoints.web.base-path=/application # Base path for Web endpoints. Relative to server.context-path or management.server.context-path if management.server.port is configured. management.endpoints.web.base-path=/application # Base path for Web endpoints. Relative to server.context-path or management.server.context-path if management.server.port is configured.
management.endpoints.web.path=mapping= Map of endpoint IDs to the path that should expose them management.endpoints.web.path-mapping= # Mapping between endpoint IDs and the path that should expose them.
# ENDPOINTS CORS CONFIGURATION ({sc-spring-boot-actuator-autoconfigure}/endpoint/web/servlet/CorsEndpointProperties.{sc-ext}[CorsEndpointProperties]) # ENDPOINTS CORS CONFIGURATION ({sc-spring-boot-actuator-autoconfigure}/endpoint/web/servlet/CorsEndpointProperties.{sc-ext}[CorsEndpointProperties])
management.endpoints.web.cors.allow-credentials= # Set whether credentials are supported. When not set, credentials are not supported. management.endpoints.web.cors.allow-credentials= # Set whether credentials are supported. When not set, credentials are not supported.
...@@ -1146,8 +1149,8 @@ content into your application; rather pick only the properties that you need. ...@@ -1146,8 +1149,8 @@ content into your application; rather pick only the properties that you need.
management.endpoint.flyway.enabled= # Enable the flyway endpoint. management.endpoint.flyway.enabled= # Enable the flyway endpoint.
# HEALTH ENDPOINT ({sc-spring-boot-actuator}/health/HealthEndpoint.{sc-ext}[HealthEndpoint]) # HEALTH ENDPOINT ({sc-spring-boot-actuator}/health/HealthEndpoint.{sc-ext}[HealthEndpoint])
endpoints.health.cache.time-to-live=0 # Maximum time in milliseconds that a response can be cached. management.endpoint.health.cache.time-to-live=0 # Maximum time in milliseconds that a response can be cached.
endpoints.health.enabled= # Enable the health endpoint. management.endpoint.health.enabled= # Enable the health endpoint.
# HEAP DUMP ENDPOINT ({sc-spring-boot-actuator}/management/HeapDumpWebEndpoint.{sc-ext}[HeapDumpWebEndpoint]) # HEAP DUMP ENDPOINT ({sc-spring-boot-actuator}/management/HeapDumpWebEndpoint.{sc-ext}[HeapDumpWebEndpoint])
management.endpoint.heapdump.cache.time-to-live=0 # Maximum time in milliseconds that a response can be cached. management.endpoint.heapdump.cache.time-to-live=0 # Maximum time in milliseconds that a response can be cached.
...@@ -1183,11 +1186,8 @@ content into your application; rather pick only the properties that you need. ...@@ -1183,11 +1186,8 @@ content into your application; rather pick only the properties that you need.
management.endpoint.prometheus.enabled= # Enable the metrics endpoint. management.endpoint.prometheus.enabled= # Enable the metrics endpoint.
# SCHEDULED TASKS ENDPOINT ({sc-spring-boot-actuator}/scheduling/ScheduledTasksEndpoint.{sc-ext}[ScheduledTasksEndpoint]) # SCHEDULED TASKS ENDPOINT ({sc-spring-boot-actuator}/scheduling/ScheduledTasksEndpoint.{sc-ext}[ScheduledTasksEndpoint])
endpoints.scheduledtasks.cache.time-to-live=0 # Maximum time in milliseconds that a response can be cached. management.endpoint.scheduledtasks.cache.time-to-live=0 # Maximum time in milliseconds that a response can be cached.
endpoints.scheduledtasks.enabled= # Enable the scheduled tasks endpoint. management.endpoint.scheduledtasks.enabled= # Enable the scheduled tasks endpoint.
endpoints.scheduledtasks.jmx.enabled= # Expose the scheduled tasks endpoint as a JMX MBean.
endpoints.scheduledtasks.web.enabled= # Expose the scheduled tasks endpoint as a Web endpoint.
endpoints.scheduledtasks.web.path=sessions # Path of the scheduled tasks endpoint.
# SESSIONS ENDPOINT ({sc-spring-boot-actuator}/session/SessionsEndpoint.{sc-ext}[SessionsEndpoint]) # SESSIONS ENDPOINT ({sc-spring-boot-actuator}/session/SessionsEndpoint.{sc-ext}[SessionsEndpoint])
management.endpoint.sessions.cache.time-to-live=0 # Maximum time in milliseconds that a response can be cached. management.endpoint.sessions.cache.time-to-live=0 # Maximum time in milliseconds that a response can be cached.
......
...@@ -429,7 +429,7 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor ...@@ -429,7 +429,7 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
} }
private String endpointKey(String suffix) { private String endpointKey(String suffix) {
return "endpoints." + suffix; return "management.endpoint." + suffix;
} }
private boolean isNested(Element returnType, VariableElement field, private boolean isNested(Element returnType, VariableElement field,
......
...@@ -533,7 +533,7 @@ public class ConfigurationMetadataAnnotationProcessorTests { ...@@ -533,7 +533,7 @@ public class ConfigurationMetadataAnnotationProcessorTests {
public void simpleEndpoint() throws IOException { public void simpleEndpoint() throws IOException {
ConfigurationMetadata metadata = compile(SimpleEndpoint.class); ConfigurationMetadata metadata = compile(SimpleEndpoint.class);
assertThat(metadata).has( assertThat(metadata).has(
Metadata.withGroup("endpoints.simple").fromSource(SimpleEndpoint.class)); Metadata.withGroup("management.endpoint.simple").fromSource(SimpleEndpoint.class));
assertThat(metadata).has(enabledFlag("simple", true)); assertThat(metadata).has(enabledFlag("simple", true));
assertThat(metadata).has(cacheTtl("simple")); assertThat(metadata).has(cacheTtl("simple"));
assertThat(metadata.getItems()).hasSize(3); assertThat(metadata.getItems()).hasSize(3);
...@@ -542,7 +542,7 @@ public class ConfigurationMetadataAnnotationProcessorTests { ...@@ -542,7 +542,7 @@ public class ConfigurationMetadataAnnotationProcessorTests {
@Test @Test
public void disableEndpoint() throws IOException { public void disableEndpoint() throws IOException {
ConfigurationMetadata metadata = compile(DisabledEndpoint.class); ConfigurationMetadata metadata = compile(DisabledEndpoint.class);
assertThat(metadata).has(Metadata.withGroup("endpoints.disabled") assertThat(metadata).has(Metadata.withGroup("management.endpoint.disabled")
.fromSource(DisabledEndpoint.class)); .fromSource(DisabledEndpoint.class));
assertThat(metadata).has(enabledFlag("disabled", false)); assertThat(metadata).has(enabledFlag("disabled", false));
assertThat(metadata).has(cacheTtl("disabled")); assertThat(metadata).has(cacheTtl("disabled"));
...@@ -552,7 +552,7 @@ public class ConfigurationMetadataAnnotationProcessorTests { ...@@ -552,7 +552,7 @@ public class ConfigurationMetadataAnnotationProcessorTests {
@Test @Test
public void enabledEndpoint() throws IOException { public void enabledEndpoint() throws IOException {
ConfigurationMetadata metadata = compile(EnabledEndpoint.class); ConfigurationMetadata metadata = compile(EnabledEndpoint.class);
assertThat(metadata).has(Metadata.withGroup("endpoints.enabled") assertThat(metadata).has(Metadata.withGroup("management.endpoint.enabled")
.fromSource(EnabledEndpoint.class)); .fromSource(EnabledEndpoint.class));
assertThat(metadata).has(enabledFlag("enabled", true)); assertThat(metadata).has(enabledFlag("enabled", true));
assertThat(metadata).has(cacheTtl("enabled")); assertThat(metadata).has(cacheTtl("enabled"));
...@@ -562,9 +562,10 @@ public class ConfigurationMetadataAnnotationProcessorTests { ...@@ -562,9 +562,10 @@ public class ConfigurationMetadataAnnotationProcessorTests {
@Test @Test
public void customPropertiesEndpoint() throws IOException { public void customPropertiesEndpoint() throws IOException {
ConfigurationMetadata metadata = compile(CustomPropertiesEndpoint.class); ConfigurationMetadata metadata = compile(CustomPropertiesEndpoint.class);
assertThat(metadata).has(Metadata.withGroup("endpoints.customprops") assertThat(metadata).has(Metadata.withGroup("management.endpoint.customprops")
.fromSource(CustomPropertiesEndpoint.class)); .fromSource(CustomPropertiesEndpoint.class));
assertThat(metadata).has(Metadata.withProperty("endpoints.customprops.name") assertThat(metadata).has(Metadata
.withProperty("management.endpoint.customprops.name")
.ofType(String.class).withDefaultValue("test")); .ofType(String.class).withDefaultValue("test"));
assertThat(metadata).has(enabledFlag("customprops", true)); assertThat(metadata).has(enabledFlag("customprops", true));
assertThat(metadata).has(cacheTtl("customprops")); assertThat(metadata).has(cacheTtl("customprops"));
...@@ -574,7 +575,7 @@ public class ConfigurationMetadataAnnotationProcessorTests { ...@@ -574,7 +575,7 @@ public class ConfigurationMetadataAnnotationProcessorTests {
@Test @Test
public void specificEndpoint() throws IOException { public void specificEndpoint() throws IOException {
ConfigurationMetadata metadata = compile(SpecificEndpoint.class); ConfigurationMetadata metadata = compile(SpecificEndpoint.class);
assertThat(metadata).has(Metadata.withGroup("endpoints.specific") assertThat(metadata).has(Metadata.withGroup("management.endpoint.specific")
.fromSource(SpecificEndpoint.class)); .fromSource(SpecificEndpoint.class));
assertThat(metadata).has(enabledFlag("specific", true)); assertThat(metadata).has(enabledFlag("specific", true));
assertThat(metadata).has(cacheTtl("specific")); assertThat(metadata).has(cacheTtl("specific"));
...@@ -586,7 +587,7 @@ public class ConfigurationMetadataAnnotationProcessorTests { ...@@ -586,7 +587,7 @@ public class ConfigurationMetadataAnnotationProcessorTests {
TestProject project = new TestProject(this.temporaryFolder, TestProject project = new TestProject(this.temporaryFolder,
IncrementalEndpoint.class); IncrementalEndpoint.class);
ConfigurationMetadata metadata = project.fullBuild(); ConfigurationMetadata metadata = project.fullBuild();
assertThat(metadata).has(Metadata.withGroup("endpoints.incremental") assertThat(metadata).has(Metadata.withGroup("management.endpoint.incremental")
.fromSource(IncrementalEndpoint.class)); .fromSource(IncrementalEndpoint.class));
assertThat(metadata).has(enabledFlag("incremental", true)); assertThat(metadata).has(enabledFlag("incremental", true));
assertThat(metadata).has(cacheTtl("incremental")); assertThat(metadata).has(cacheTtl("incremental"));
...@@ -594,7 +595,7 @@ public class ConfigurationMetadataAnnotationProcessorTests { ...@@ -594,7 +595,7 @@ public class ConfigurationMetadataAnnotationProcessorTests {
project.replaceText(IncrementalEndpoint.class, "id = \"incremental\"", project.replaceText(IncrementalEndpoint.class, "id = \"incremental\"",
"id = \"incremental\", enableByDefault = false"); "id = \"incremental\", enableByDefault = false");
metadata = project.incrementalBuild(IncrementalEndpoint.class); metadata = project.incrementalBuild(IncrementalEndpoint.class);
assertThat(metadata).has(Metadata.withGroup("endpoints.incremental") assertThat(metadata).has(Metadata.withGroup("management.endpoint.incremental")
.fromSource(IncrementalEndpoint.class)); .fromSource(IncrementalEndpoint.class));
assertThat(metadata).has(enabledFlag("incremental", false)); assertThat(metadata).has(enabledFlag("incremental", false));
assertThat(metadata).has(cacheTtl("incremental")); assertThat(metadata).has(cacheTtl("incremental"));
...@@ -606,7 +607,7 @@ public class ConfigurationMetadataAnnotationProcessorTests { ...@@ -606,7 +607,7 @@ public class ConfigurationMetadataAnnotationProcessorTests {
TestProject project = new TestProject(this.temporaryFolder, TestProject project = new TestProject(this.temporaryFolder,
SpecificEndpoint.class); SpecificEndpoint.class);
ConfigurationMetadata metadata = project.fullBuild(); ConfigurationMetadata metadata = project.fullBuild();
assertThat(metadata).has(Metadata.withGroup("endpoints.specific") assertThat(metadata).has(Metadata.withGroup("management.endpoint.specific")
.fromSource(SpecificEndpoint.class)); .fromSource(SpecificEndpoint.class));
assertThat(metadata).has(enabledFlag("specific", true)); assertThat(metadata).has(enabledFlag("specific", true));
assertThat(metadata).has(cacheTtl("specific")); assertThat(metadata).has(cacheTtl("specific"));
...@@ -614,7 +615,7 @@ public class ConfigurationMetadataAnnotationProcessorTests { ...@@ -614,7 +615,7 @@ public class ConfigurationMetadataAnnotationProcessorTests {
project.replaceText(SpecificEndpoint.class, "enableByDefault = true", project.replaceText(SpecificEndpoint.class, "enableByDefault = true",
"enableByDefault = false"); "enableByDefault = false");
metadata = project.incrementalBuild(SpecificEndpoint.class); metadata = project.incrementalBuild(SpecificEndpoint.class);
assertThat(metadata).has(Metadata.withGroup("endpoints.specific") assertThat(metadata).has(Metadata.withGroup("management.endpoint.specific")
.fromSource(SpecificEndpoint.class)); .fromSource(SpecificEndpoint.class));
assertThat(metadata).has(enabledFlag("specific", false)); assertThat(metadata).has(enabledFlag("specific", false));
assertThat(metadata).has(cacheTtl("specific")); assertThat(metadata).has(cacheTtl("specific"));
...@@ -623,13 +624,14 @@ public class ConfigurationMetadataAnnotationProcessorTests { ...@@ -623,13 +624,14 @@ public class ConfigurationMetadataAnnotationProcessorTests {
private Metadata.MetadataItemCondition enabledFlag(String endpointId, private Metadata.MetadataItemCondition enabledFlag(String endpointId,
Boolean defaultValue) { Boolean defaultValue) {
return Metadata.withEnabledFlag("endpoints." + endpointId + ".enabled") return Metadata.withEnabledFlag("management.endpoint." + endpointId + ".enabled")
.withDefaultValue(defaultValue) .withDefaultValue(defaultValue)
.withDescription(String.format("Enable the %s endpoint.", endpointId)); .withDescription(String.format("Enable the %s endpoint.", endpointId));
} }
private Metadata.MetadataItemCondition cacheTtl(String endpointId) { private Metadata.MetadataItemCondition cacheTtl(String endpointId) {
return Metadata.withProperty("endpoints." + endpointId + ".cache.time-to-live") return Metadata
.withProperty("management.endpoint." + endpointId + ".cache.time-to-live")
.ofType(Long.class).withDefaultValue(0).withDescription( .ofType(Long.class).withDefaultValue(0).withDescription(
"Maximum time in milliseconds that a response can be cached."); "Maximum time in milliseconds that a response can be cached.");
} }
......
...@@ -25,7 +25,7 @@ import org.springframework.boot.configurationsample.Endpoint; ...@@ -25,7 +25,7 @@ import org.springframework.boot.configurationsample.Endpoint;
* @author Stephane Nicoll * @author Stephane Nicoll
*/ */
@Endpoint(id = "customprops") @Endpoint(id = "customprops")
@ConfigurationProperties("endpoints.customprops") @ConfigurationProperties("management.endpoint.customprops")
public class CustomPropertiesEndpoint { public class CustomPropertiesEndpoint {
private String name = "test"; private String name = "test";
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment