Closes #229
Merge brooklyn.parameters in YAML files
Implements behaviour required to merge `brooklyn.parameters` sections of specifications in YAML files, allowing addition of new parameters to entities without having to specify the complete list again.
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshCommandEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshCommandEffector.java
index ca952fe..957d68e 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshCommandEffector.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshCommandEffector.java
@@ -19,34 +19,39 @@
package org.apache.brooklyn.core.effector.ssh;
import java.util.Map;
+import java.util.concurrent.ExecutionException;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicates;
+import com.google.common.collect.Maps;
import org.apache.brooklyn.api.effector.Effector;
import org.apache.brooklyn.api.effector.ParameterType;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.config.MapConfigKey;
import org.apache.brooklyn.core.effector.AddEffector;
import org.apache.brooklyn.core.effector.EffectorBody;
import org.apache.brooklyn.core.effector.Effectors;
import org.apache.brooklyn.core.effector.Effectors.EffectorBuilder;
import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
-import org.apache.brooklyn.core.entity.EntityInternal;
import org.apache.brooklyn.core.sensor.ssh.SshCommandSensor;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.core.config.ConfigBag;
import org.apache.brooklyn.util.core.json.ShellEnvironmentSerializer;
-import org.apache.brooklyn.util.text.Strings;
-
-import com.google.common.base.Preconditions;
+import org.apache.brooklyn.util.core.task.Tasks;
+import org.apache.brooklyn.util.exceptions.Exceptions;
public final class SshCommandEffector extends AddEffector {
-
+
public static final ConfigKey<String> EFFECTOR_COMMAND = ConfigKeys.newStringConfigKey("command");
public static final ConfigKey<String> EFFECTOR_EXECUTION_DIR = SshCommandSensor.SENSOR_EXECUTION_DIR;
-
+ public static final MapConfigKey<Object> EFFECTOR_SHELL_ENVIRONMENT = BrooklynConfigKeys.SHELL_ENVIRONMENT;
+
public SshCommandEffector(ConfigBag params) {
super(newEffectorBuilder(params).build());
}
-
+
public SshCommandEffector(Map<String,String> params) {
this(ConfigBag.newInstance(params));
}
@@ -57,7 +62,6 @@
return eff;
}
-
protected static class Body extends EffectorBody<String> {
private final Effector<?> effector;
private final String command;
@@ -65,42 +69,48 @@
public Body(Effector<?> eff, ConfigBag params) {
this.effector = eff;
- this.command = Preconditions.checkNotNull(params.get(EFFECTOR_COMMAND), "command must be supplied when defining this effector");
+ this.command = Preconditions.checkNotNull(params.get(EFFECTOR_COMMAND), "SSH command must be supplied when defining this effector");
this.executionDir = params.get(EFFECTOR_EXECUTION_DIR);
- // TODO could take a custom "env" aka effectorShellEnv
}
@Override
public String call(ConfigBag params) {
- String command = this.command;
-
- command = SshCommandSensor.makeCommandExecutingInDirectory(command, executionDir, entity());
-
- MutableMap<String, String> env = MutableMap.of();
- // first set all declared parameters, including default values
- for (ParameterType<?> param: effector.getParameters()) {
- env.addIfNotNull(param.getName(), Strings.toString( params.get(Effectors.asConfigKey(param)) ));
+ String sshCommand = SshCommandSensor.makeCommandExecutingInDirectory(command, executionDir, entity());
+
+ MutableMap<String, Object> env = MutableMap.of();
+
+ // Set all declared parameters, including default values
+ for (ParameterType<?> param : effector.getParameters()) {
+ env.addIfNotNull(param.getName(), params.get(Effectors.asConfigKey(param)));
}
-
- // then set things from the entities defined shell environment, if applicable
- Map<String, Object> shellEnv = entity().getConfig(BrooklynConfigKeys.SHELL_ENVIRONMENT);
- ShellEnvironmentSerializer envSerializer = new ShellEnvironmentSerializer(((EntityInternal)entity()).getManagementContext());
- env.putAll(envSerializer.serialize(shellEnv));
-
- // if we wanted to resolve the surrounding environment in real time -- see above
-// Map<String,Object> paramsResolved = (Map<String, Object>) Tasks.resolveDeepValue(effectorShellEnv, Map.class, entity().getExecutionContext());
-
- // finally set the parameters we've been passed; this will repeat declared parameters but to no harm,
- // it may pick up additional values (could be a flag defining whether this is permitted or not)
- env.putAll(Strings.toStringMap(params.getAllConfig()));
-
- SshEffectorTasks.SshEffectorTaskFactory<String> t = SshEffectorTasks.ssh(command)
- .requiringZeroAndReturningStdout()
- .summary("effector "+effector.getName())
- .environmentVariables(env);
- return queue(t).get();
+
+ // Set things from the entities defined shell environment, if applicable
+ env.putAll(entity().config().get(BrooklynConfigKeys.SHELL_ENVIRONMENT));
+
+ // Add the shell environment entries from our configuration
+ Map<String, Object> effectorEnv = params.get(EFFECTOR_SHELL_ENVIRONMENT);
+ if (effectorEnv != null) env.putAll(effectorEnv);
+
+ // Set the parameters we've been passed. This will repeat declared parameters but to no harm,
+ // it may pick up additional values (could be a flag defining whether this is permitted or not.)
+ // Make sure we do not include the shell.env here again, by filtering it out.
+ env.putAll(Maps.filterKeys(params.getAllConfig(), Predicates.not(Predicates.equalTo(EFFECTOR_SHELL_ENVIRONMENT.getName()))));
+
+ // Try to resolve the configuration in the env Map
+ try {
+ env = (MutableMap<String, Object>) Tasks.resolveDeepValue(env, Object.class, entity().getExecutionContext());
+ } catch (InterruptedException | ExecutionException e) {
+ Exceptions.propagateIfFatal(e);
+ }
+
+ // Execute the effector with the serialized environment strings
+ ShellEnvironmentSerializer serializer = new ShellEnvironmentSerializer(entity().getManagementContext());
+ SshEffectorTasks.SshEffectorTaskFactory<String> task = SshEffectorTasks.ssh(sshCommand)
+ .requiringZeroAndReturningStdout()
+ .summary("effector "+effector.getName())
+ .environmentVariables(serializer.serialize(env));
+
+ return queue(task).get();
}
-
}
-
}
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 3464281..8b1d410 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
@@ -19,6 +19,7 @@
package org.apache.brooklyn.core.sensor.ssh;
import java.util.Map;
+import java.util.concurrent.ExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -33,6 +34,7 @@
import org.apache.brooklyn.api.entity.EntityLocal;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.config.MapConfigKey;
import org.apache.brooklyn.core.effector.AddSensor;
import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
import org.apache.brooklyn.core.entity.EntityInternal;
@@ -44,6 +46,8 @@
import org.apache.brooklyn.util.core.config.ConfigBag;
import org.apache.brooklyn.util.core.flags.TypeCoercions;
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.os.Os;
import org.apache.brooklyn.util.text.Strings;
@@ -63,17 +67,19 @@
public static final ConfigKey<String> SENSOR_EXECUTION_DIR = ConfigKeys.newStringConfigKey("executionDir", "Directory where the command should run; "
+ "if not supplied, executes in the entity's run dir (or home dir if no run dir is defined); "
+ "use '~' to always execute in the home dir, or 'custom-feed/' to execute in a custom-feed dir relative to the run dir");
+ public static final MapConfigKey<Object> SENSOR_SHELL_ENVIRONMENT = BrooklynConfigKeys.SHELL_ENVIRONMENT;
protected final String command;
protected final String executionDir;
+ protected final Map<String,Object> sensorEnv;
public SshCommandSensor(final ConfigBag params) {
super(params);
// TODO create a supplier for the command string to support attribute embedding
- command = Preconditions.checkNotNull(params.get(SENSOR_COMMAND), "command");
-
+ command = Preconditions.checkNotNull(params.get(SENSOR_COMMAND), "SSH command must be dupplied when defining this sensor");
executionDir = params.get(SENSOR_EXECUTION_DIR);
+ sensorEnv = params.get(SENSOR_SHELL_ENVIRONMENT);
}
@Override
@@ -87,9 +93,21 @@
Supplier<Map<String,String>> envSupplier = new Supplier<Map<String,String>>() {
@Override
public Map<String, String> get() {
- Map<String, Object> env = entity.getConfig(BrooklynConfigKeys.SHELL_ENVIRONMENT);
- ShellEnvironmentSerializer envSerializer = new ShellEnvironmentSerializer(((EntityInternal)entity).getManagementContext());
- return envSerializer.serialize(env);
+ Map<String, Object> env = MutableMap.copyOf(entity.getConfig(BrooklynConfigKeys.SHELL_ENVIRONMENT));
+
+ // Add the shell environment entries from our configuration
+ if (sensorEnv != null) env.putAll(sensorEnv);
+
+ // Try to resolve the configuration in the env Map
+ try {
+ env = (Map<String, Object>) Tasks.resolveDeepValue(env, Object.class, ((EntityInternal) entity).getExecutionContext());
+ } catch (InterruptedException | ExecutionException e) {
+ Exceptions.propagateIfFatal(e);
+ }
+
+ // Convert the environment into strings with the serializer
+ ShellEnvironmentSerializer serializer = new ShellEnvironmentSerializer(((EntityInternal) entity).getManagementContext());
+ return serializer.serialize(env);
}
};
@@ -109,7 +127,7 @@
.onSuccess(Functions.compose(new Function<String, T>() {
@Override
public T apply(String input) {
- return TypeCoercions.coerce(Strings.trimEnd(input), getType(entity, type));
+ return TypeCoercions.coerce(Strings.trimEnd(input), (Class<T>) sensor.getType());
}}, SshValueFunctions.stdout()));
SshFeed.builder()