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();
     }
-    
+
 }