You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2021/10/25 12:27:55 UTC
[brooklyn-server] 04/15: add option for ssh command sensor to parse
as yaml
This is an automated email from the ASF dual-hosted git repository.
heneveld pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brooklyn-server.git
commit 5373eadd0a41f3a59e20bf2a99505a28b7c454dd
Author: Alex Heneveld <al...@cloudsoftcorp.com>
AuthorDate: Tue Oct 19 16:38:43 2021 +0100
add option for ssh command sensor to parse as yaml
by default tries as yaml for complex types, looking after any --- token; then falls back to string parse;
new config keys allow exact backwards compatibility or not ignoring text before any ---
previously would only attempt coercion from string to the type, which would not work well if the type was a bean and input was yaml
(yaml string coercion only works for going to maps or lists)
---
.../brooklyn/core/sensor/ssh/SshCommandSensor.java | 68 ++++++++++++++++++++--
.../java/org/apache/brooklyn/util/yaml/Yamls.java | 25 +++++++-
.../org/apache/brooklyn/util/yaml/YamlsTest.java | 10 +++-
3 files changed, 97 insertions(+), 6 deletions(-)
diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java b/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java
index 6d900fc..cb631d5 100644
--- a/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java
+++ b/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java
@@ -18,6 +18,7 @@
*/
package org.apache.brooklyn.core.sensor.ssh;
+import com.google.common.collect.Iterables;
import java.util.Map;
import java.util.concurrent.ExecutionException;
@@ -43,10 +44,14 @@ import org.apache.brooklyn.util.core.json.ShellEnvironmentSerializer;
import org.apache.brooklyn.util.core.task.Tasks;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.guava.Functionals;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.guava.TypeTokens;
+import org.apache.brooklyn.util.javalang.Boxing;
import org.apache.brooklyn.util.os.Os;
import org.apache.brooklyn.util.text.StringFunctions;
import org.apache.brooklyn.util.text.Strings;
import org.apache.brooklyn.util.time.Duration;
+import org.apache.brooklyn.util.yaml.Yamls;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -77,6 +82,13 @@ public final class SshCommandSensor<T> extends AbstractAddSensorFeed<T> {
public static final ConfigKey<Object> VALUE_ON_ERROR = ConfigKeys.newConfigKey(Object.class, "value.on.error",
"Value to be used if an error occurs whilst executing the ssh command", null);
public static final MapConfigKey<Object> SENSOR_SHELL_ENVIRONMENT = BrooklynConfigKeys.SHELL_ENVIRONMENT;
+ public static final ConfigKey<String> FORMAT = ConfigKeys.newStringConfigKey("format",
+ "Format to expect for the output; default to auto which will attempt a yaml/json parse for complex types, falling back to string, then coerce; " +
+ "other options are just 'string' (previous default) or 'yaml'", "auto");
+ public static final ConfigKey<Boolean> LAST_YAML_DOCUMENT = ConfigKeys.newBooleanConfigKey("useLastYaml",
+ "Whether to trim the output ignoring everything up to and before the last `---` line if present when expecting yaml; " +
+ "useful if the script has quite a lot of output which should be ignored prior, with the value to be used for the sensor output last; " +
+ "default true (ignored if format is 'string')", true);
protected SshCommandSensor() {}
public SshCommandSensor(ConfigBag params) {
@@ -109,10 +121,7 @@ public final class SshCommandSensor<T> extends AbstractAddSensorFeed<T> {
.suppressDuplicates(Boolean.TRUE.equals(suppressDuplicates))
.checkSuccess(SshValueFunctions.exitStatusEquals(0))
.onFailureOrException(Functions.constant((T)params.get(VALUE_ON_ERROR)))
- .onSuccess(Functionals.chain(
- SshValueFunctions.stdout(),
- StringFunctions.trimEnd(),
- TypeCoercions.function((Class<T>) sensor.getType())))
+ .onSuccess(Functionals.chain(SshValueFunctions.stdout(), new CoerceOutputFunction<>(sensor.getTypeToken(), initParam(FORMAT), initParam(LAST_YAML_DOCUMENT))))
.logWarningGraceTimeOnStartup(logWarningGraceTimeOnStartup)
.logWarningGraceTime(logWarningGraceTime);
@@ -171,6 +180,57 @@ public final class SshCommandSensor<T> extends AbstractAddSensorFeed<T> {
}
@Beta
+ public static class CoerceOutputFunction<T> implements Function<String,T> {
+ final TypeToken<T> typeToken;
+ final String format;
+ final Boolean useLastYamlDocument;
+
+ public CoerceOutputFunction(TypeToken<T> typeToken, String format, Boolean useLastYamlDocument) {
+ this.typeToken = typeToken;
+ this.format = format;
+ this.useLastYamlDocument = useLastYamlDocument;
+ }
+
+ public T apply(String input) {
+ boolean doYaml = !"string".equalsIgnoreCase(format);
+ boolean doString = !"yaml".equalsIgnoreCase(format);
+
+ if ("auto".equalsIgnoreCase(format)) {
+ if (String.class.equals(typeToken.getRawType()) || Boxing.isPrimitiveOrBoxedClass(typeToken.getRawType())) {
+ // don't do yaml if we want a string or a primitive
+ doYaml = false;
+ }
+ }
+
+ Maybe<T> result1 = null;
+
+ if (doYaml) {
+ try {
+ String yamlInS = input;
+ if (!Boolean.FALSE.equals(useLastYamlDocument)) {
+ yamlInS = Yamls.lastDocumentFunction().apply(yamlInS);
+ }
+ Object yamlInO = Iterables.getOnlyElement(Yamls.parseAll(yamlInS));
+ result1 = TypeCoercions.tryCoerce(yamlInO, typeToken);
+ if (result1.isPresent()) doString = false;
+ } catch (Exception e) {
+ if (result1==null) result1 = Maybe.absent(e);
+ }
+ }
+
+ if (doString) {
+ try {
+ return (T) Functionals.chain(StringFunctions.trimEnd(), TypeCoercions.function(typeToken.getRawType())).apply(input);
+ } catch (Exception e) {
+ if (result1==null) result1 = Maybe.absent(e);
+ }
+ }
+
+ return result1.get();
+ }
+ }
+
+ @Beta
public static String makeCommandExecutingInDirectory(String command, String executionDir, Entity entity) {
String finalCommand = command;
String execDir = executionDir;
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yaml/Yamls.java b/utils/common/src/main/java/org/apache/brooklyn/util/yaml/Yamls.java
index 986d9b3..5e92b11 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/util/yaml/Yamls.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/yaml/Yamls.java
@@ -18,6 +18,7 @@
*/
package org.apache.brooklyn.util.yaml;
+import com.google.common.base.Function;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
@@ -28,6 +29,8 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import javax.annotation.Nullable;
import org.apache.brooklyn.util.collections.Jsonya;
@@ -587,7 +590,7 @@ b: 1
* this will find the YAML text for that element
* <p>
* If not found this will return a {@link YamlExtract}
- * where {@link YamlExtract#isMatch()} is false and {@link YamlExtract#getError()} is set. */
+ * where {@link YamlExtract#found()} is false and {@link YamlExtract#getError()} is set. */
public static YamlExtract getTextOfYamlAtPath(String yaml, Object ...path) {
YamlExtract result = new YamlExtract();
if (yaml==null) return result;
@@ -608,4 +611,24 @@ b: 1
return result;
}
}
+
+ static class LastDocumentFunction implements Function<String,String> {
+
+ @Override
+ public String apply(String input) {
+ if (input==null) return null;
+ Matcher match = Pattern.compile("^---$[\\n\\r]?", Pattern.MULTILINE).matcher(input);
+ int lastEnd = 0;
+ while (match.find()) {
+ lastEnd = match.end();
+ }
+ return input.substring(lastEnd);
+ }
+ }
+ private static final LastDocumentFunction LAST_DOCUMENT_FUNCTION_INSTANCE = new LastDocumentFunction();
+
+ public static Function<String,String> lastDocumentFunction() {
+ return LAST_DOCUMENT_FUNCTION_INSTANCE;
+ }
+
}
diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yaml/YamlsTest.java b/utils/common/src/test/java/org/apache/brooklyn/util/yaml/YamlsTest.java
index 50e499e..4605708 100644
--- a/utils/common/src/test/java/org/apache/brooklyn/util/yaml/YamlsTest.java
+++ b/utils/common/src/test/java/org/apache/brooklyn/util/yaml/YamlsTest.java
@@ -201,6 +201,14 @@ public class YamlsTest {
}
}
+ @Test
+ public void testLastDocument() {
+ Asserts.assertEquals(Yamls.lastDocumentFunction().apply("foo\n---\nbar"), "bar");
+ Asserts.assertEquals(Yamls.lastDocumentFunction().apply("foo\n--- \nbar"), "foo\n--- \nbar");
+ Asserts.assertEquals(Yamls.lastDocumentFunction().apply("foo"), "foo");
+ Asserts.assertEquals(Yamls.lastDocumentFunction().apply(null), null);
+ }
+
// convenience, since running with older TestNG IDE plugin will fail (older snakeyaml dependency);
// if you run as a java app it doesn't bring in the IDE TestNG jar version, and it works
public static void main(String[] args) {
@@ -209,5 +217,5 @@ public class YamlsTest {
// testng.setVerbose(9);
testng.run();
}
-
+
}