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 <[email protected]> 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(); } - + }
