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 2022/07/29 13:53:33 UTC

[brooklyn-server] branch master updated (5a75cfbd3d -> 21da506859)

This is an automated email from the ASF dual-hosted git repository.

heneveld pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/brooklyn-server.git


    from 5a75cfbd3d better type inference in jackson deserialization
     new b81787c28b refactor BashCommands to have non-static, context sensitive evaluation
     new 11d1949f14 update other places that ignore certs to use BashCommandsConfigurable settings detection
     new e2084ef0da update other files to use new BashCommandsConfigurable rather than statics
     new f875953201 misc clean-up for script constants
     new 401f26d347 tweak signatures so machines are preferred over entities, and short key name accepted
     new 21da506859 allow use of trust all to be configured via brooklyn properties and entity config

The 6 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../catalog/internal/BasicBrooklynCatalog.java     |    2 +-
 .../PropertiesFileExternalConfigSupplier.java      |    4 +-
 .../vault/VaultExternalConfigSupplier.java         |    3 +-
 .../core/effector/http/HttpCommandEffector.java    |   54 +-
 .../core/effector/ssh/SshEffectorTasks.java        |   34 +-
 .../brooklyn/core/entity/BrooklynConfigKeys.java   |   18 +-
 .../core/location/LocationConfigUtils.java         |   35 +-
 .../mgmt/ha/BrooklynBomOsgiArchiveInstaller.java   |    2 +-
 .../org/apache/brooklyn/feed/http/HttpFeed.java    |   60 +-
 .../LocalhostMachineProvisioningLocation.java      |    2 +-
 .../brooklyn/location/ssh/SshMachineLocation.java  |   18 +-
 .../apache/brooklyn/util/core/ResourceUtils.java   |   38 +-
 .../brooklyn/util/core/file/ArchiveUtils.java      |   15 +-
 .../util/core/file/BrooklynOsCommands.java         |   90 ++
 .../util/core/internal/ssh/ShellAbstractTool.java  |    1 +
 .../util/core/javalang/BrooklynHttpConfig.java     |  155 +++
 .../brooklyn/util/core/osgi/BundleMaker.java       |    2 +-
 .../util/core/task/ssh/SshPutTaskFactory.java      |    7 +
 .../brooklyn/util/core/task/ssh/SshTasks.java      |   21 +-
 .../ssh/internal/AbstractSshExecTaskFactory.java   |    4 +-
 .../util/core/task/system/ProcessTaskFactory.java  |    2 +
 .../util/core/task/system/ProcessTaskStub.java     |   11 +-
 .../util/core/task/system/ProcessTaskWrapper.java  |    2 +-
 .../internal/AbstractProcessTaskFactory.java       |   14 +-
 .../system/internal/SystemProcessTaskFactory.java  |    4 +-
 .../util/executor/HttpExecutorFactoryImpl.java     |   10 +-
 .../persist/deserializingClassRenames.properties   |    4 +-
 .../core/effector/ssh/SshEffectorTasksTest.java    |    5 +-
 .../ssh/SshMachineLocationSshToolTest.java         |   25 +-
 .../brooklyn/util/core/ResourceUtilsTest.java      |    2 +-
 .../util/core/ssh/BashCommandsIntegrationTest.java |  109 +-
 .../location/jclouds/CreateUserStatements.java     |    3 +-
 .../brooklyn/location/jclouds/JcloudsLocation.java |   49 +-
 .../jclouds/JcloudsSshMachineLocation.java         |    3 +-
 .../policy/jclouds/os/CreateUserPolicy.java        |   11 +-
 .../location/jclouds/JcloudsSuseLiveTest.java      |    4 +-
 ...eJcloudsLocationUserLoginAndConfigLiveTest.java |    5 +-
 .../jclouds/provider/CarrenzaLocationLiveTest.java |    3 +-
 .../entity/brooklynnode/DeployBlueprintTest.java   |    4 +-
 .../brooklyn/rest/BrooklynRestApiLauncherTest.java |    2 +-
 .../brooklyn/entity/brooklynnode/BrooklynNode.java |    4 +-
 .../entity/brooklynnode/BrooklynNodeSshDriver.java |    7 +-
 .../entity/brooklynnode/EntityHttpClientImpl.java  |    6 +-
 .../entity/java/JavaSoftwareProcessSshDriver.java  |    7 +-
 .../brooklyn/entity/machine/MachineInitTasks.java  |   31 +-
 .../entity/machine/SetHostnameCustomizer.java      |   12 +-
 .../entity/machine/SetLimitsCustomizer.java        |    4 +-
 .../base/AbstractSoftwareProcessSshDriver.java     |   26 +-
 .../base/VanillaSoftwareProcessSshDriver.java      |    8 +-
 .../system_service/InitdServiceInstaller.java      |   14 +-
 .../entity/AbstractGoogleComputeLiveTest.java      |    3 +-
 .../entity/AbstractMultiDistroLiveTest.java        |    8 +-
 .../brooklyn/entity/AbstractSoftlayerLiveTest.java |    3 +-
 .../brooklynnode/BrooklynNodeIntegrationTest.java  |   23 +-
 .../machine/SetHostnameCustomizerLiveTest.java     |    2 +-
 .../entity/machine/pool/ServerPoolLiveTest.java    |    3 +-
 .../software/base/AbstractDockerLiveTest.java      |    3 +-
 ...wareProcessStopsDuringStartJcloudsLiveTest.java |    2 +-
 .../base/test/location/SecurityGroupLiveTest.java  |    3 +-
 .../test/mysql/DynamicToyMySqlEntityBuilder.java   |   21 +-
 .../system_service/SystemServiceEnricherTest.java  |    4 +-
 .../org/apache/brooklyn/test/HttpTestUtils.java    |  138 +--
 .../org/apache/brooklyn/test/WebAppMonitor.java    |    3 +-
 .../org/apache/brooklyn/util/http/HttpAsserts.java |   16 +-
 .../org/apache/brooklyn/util/http/HttpTool.java    |   50 +-
 .../util/http/TrustingSslSocketFactory.java        |    6 +-
 .../executor/apacheclient/HttpExecutorImpl.java    |    7 +
 .../org/apache/brooklyn/util/ssh/BashCommands.java | 1070 +++++---------------
 ...Commands.java => BashCommandsConfigurable.java} |  234 ++---
 .../apache/brooklyn/util/ssh/IptablesCommands.java |  142 +--
 ...ands.java => IptablesCommandsConfigurable.java} |   75 +-
 .../util/ssh/IptablesCommandsFirewalldTest.java    |   21 +-
 .../brooklyn/util/ssh/IptablesCommandsTest.java    |   20 +-
 .../brooklyn/util/jmx/jmxmp/JmxmpClient.java       |   11 +-
 74 files changed, 1285 insertions(+), 1539 deletions(-)
 create mode 100644 core/src/main/java/org/apache/brooklyn/util/core/file/BrooklynOsCommands.java
 create mode 100644 core/src/main/java/org/apache/brooklyn/util/core/javalang/BrooklynHttpConfig.java
 copy utils/common/src/main/java/org/apache/brooklyn/util/ssh/{BashCommands.java => BashCommandsConfigurable.java} (82%)
 copy utils/common/src/main/java/org/apache/brooklyn/util/ssh/{IptablesCommands.java => IptablesCommandsConfigurable.java} (69%)


[brooklyn-server] 04/06: misc clean-up for script constants

Posted by he...@apache.org.
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 f875953201947729ba73bfd2f6f1ea8d9255a970
Author: Alex Heneveld <al...@cloudsoft.io>
AuthorDate: Fri Jul 29 10:47:10 2022 +0100

    misc clean-up for script constants
    
    use variables, introduce tests, better comments, in the course of reviewing and updating docs for this area
---
 .../brooklyn/core/entity/BrooklynConfigKeys.java   | 18 ++++++++--------
 .../util/core/file/BrooklynOsCommands.java         |  7 +++---
 .../ssh/SshMachineLocationSshToolTest.java         | 25 ++++++++++++++++++++--
 .../jclouds/provider/CarrenzaLocationLiveTest.java |  3 ++-
 .../entity/AbstractGoogleComputeLiveTest.java      |  3 ++-
 .../entity/AbstractMultiDistroLiveTest.java        |  3 ++-
 .../brooklyn/entity/AbstractSoftlayerLiveTest.java |  3 ++-
 .../machine/SetHostnameCustomizerLiveTest.java     |  2 +-
 .../entity/machine/pool/ServerPoolLiveTest.java    |  3 ++-
 .../software/base/AbstractDockerLiveTest.java      |  3 ++-
 ...wareProcessStopsDuringStartJcloudsLiveTest.java |  2 +-
 .../base/test/location/SecurityGroupLiveTest.java  |  3 ++-
 12 files changed, 52 insertions(+), 23 deletions(-)

diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/BrooklynConfigKeys.java b/core/src/main/java/org/apache/brooklyn/core/entity/BrooklynConfigKeys.java
index e61c8df619..40e071219a 100644
--- a/core/src/main/java/org/apache/brooklyn/core/entity/BrooklynConfigKeys.java
+++ b/core/src/main/java/org/apache/brooklyn/core/entity/BrooklynConfigKeys.java
@@ -18,10 +18,9 @@
  */
 package org.apache.brooklyn.core.entity;
 
-import static org.apache.brooklyn.core.config.ConfigKeys.newConfigKey;
-import static org.apache.brooklyn.core.config.ConfigKeys.newConfigKeyWithPrefix;
-import static org.apache.brooklyn.core.config.ConfigKeys.newStringConfigKey;
-
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
 import org.apache.brooklyn.api.location.Location;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.BasicConfigInheritance;
@@ -35,9 +34,7 @@ import org.apache.brooklyn.util.core.internal.ssh.ShellTool;
 import org.apache.brooklyn.util.core.internal.ssh.SshTool;
 import org.apache.brooklyn.util.time.Duration;
 
-import com.google.common.annotations.Beta;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableMap;
+import static org.apache.brooklyn.core.config.ConfigKeys.*;
 
 /** Commonly used config keys, for use in entities. Similar to {@link Attributes}.
  * See also {@link BrooklynServerConfig} for config keys for controlling the server. */
@@ -87,11 +84,11 @@ public class BrooklynConfigKeys {
             .build();
 
     /**
-     * Set this configuration value to true to skip the entity startup process as with {@link #ENTITY_STARTED} if the process or
+     * Set this configuration value to true to skip the entity startup process as with {@link #SKIP_ENTITY_START} if the process or
      * service represented by the entity is already running, otherwise proceed normally. This is determined using the driver's
      * {@code isRunning()} method.
      * <p>
-     * If this key is set on a {@link Location} then all entities in that location will be treated in this way, again as with {@link #ENTITY_STARTED}.
+     * If this key is set on a {@link Location} then all entities in that location will be treated in this way, again as with {@link #SKIP_ENTITY_START}.
      *
      * @see #SKIP_ENTITY_START
      */
@@ -297,6 +294,9 @@ public class BrooklynConfigKeys {
     public static final ConfigKey<String> SSH_CONFIG_DIRECT_HEADER = newConfigKeyWithPrefix(BROOKLYN_SSH_CONFIG_KEY_PREFIX, ShellTool.PROP_DIRECT_HEADER);
     public static final ConfigKey<Boolean> SSH_CONFIG_NO_DELETE_SCRIPT = newConfigKeyWithPrefix(BROOKLYN_SSH_CONFIG_KEY_PREFIX, ShellTool.PROP_NO_DELETE_SCRIPT);
 
+    public static final ConfigKey<Boolean> SSH_CONFIG_SCRIPTS_IGNORE_CERTS = newBooleanConfigKey(BROOKLYN_SSH_CONFIG_KEY_PREFIX + "scripts.ignoreCerts",
+            "Whether to generate OS commands that ignore certs, e.g. curl -k");
+
     public static final MapConfigKey<Object> PROVISIONING_PROPERTIES = new MapConfigKey.Builder<Object>(Object.class, "provisioning.properties")
             .description("Custom properties to be passed in to the location when provisioning a new machine")
             .defaultValue(ImmutableMap.<String, Object>of())
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/file/BrooklynOsCommands.java b/core/src/main/java/org/apache/brooklyn/util/core/file/BrooklynOsCommands.java
index 54ecd2a9f7..ca636becc8 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/file/BrooklynOsCommands.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/file/BrooklynOsCommands.java
@@ -22,6 +22,7 @@ import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.mgmt.ManagementContext;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
 import org.apache.brooklyn.core.entity.EntityInternal;
 import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
 import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
@@ -29,10 +30,10 @@ import org.apache.brooklyn.util.ssh.IptablesCommandsConfigurable;
 
 public class BrooklynOsCommands {
 
-    public static final ConfigKey<Boolean> OS_COMMANDS_IGNORE_CERTS = ConfigKeys.newBooleanConfigKey("brooklyn.os.commands.ignoreCerts", "Whether to generate OS commands that ignore certs, e.g. curl -k");
+    public static final ConfigKey<Boolean> SSH_CONFIG_SCRIPT_IGNORE_CERTS = BrooklynConfigKeys.SSH_CONFIG_SCRIPTS_IGNORE_CERTS;
 
     public static BashCommandsConfigurable bash(ManagementContext mgmt) {
-        return BashCommandsConfigurable.newInstance().withIgnoreCerts( ((ManagementContextInternal)mgmt).getBrooklynProperties().getConfig(OS_COMMANDS_IGNORE_CERTS) );
+        return BashCommandsConfigurable.newInstance().withIgnoreCerts( ((ManagementContextInternal)mgmt).getBrooklynProperties().getConfig(SSH_CONFIG_SCRIPT_IGNORE_CERTS) );
     }
 
     public static IptablesCommandsConfigurable bashIptables(ManagementContext mgmt) {
@@ -40,7 +41,7 @@ public class BrooklynOsCommands {
     }
 
     public static BashCommandsConfigurable bash(Entity entity) {
-        Boolean ignoreCerts = entity.config().get(OS_COMMANDS_IGNORE_CERTS);
+        Boolean ignoreCerts = entity.config().get(SSH_CONFIG_SCRIPT_IGNORE_CERTS);
         if (ignoreCerts!=null) return BashCommandsConfigurable.newInstance().withIgnoreCerts(ignoreCerts);
         return bash( ((EntityInternal)entity).getManagementContext() );
     }
diff --git a/core/src/test/java/org/apache/brooklyn/location/ssh/SshMachineLocationSshToolTest.java b/core/src/test/java/org/apache/brooklyn/location/ssh/SshMachineLocationSshToolTest.java
index 20b3f290a2..7f94891449 100644
--- a/core/src/test/java/org/apache/brooklyn/location/ssh/SshMachineLocationSshToolTest.java
+++ b/core/src/test/java/org/apache/brooklyn/location/ssh/SshMachineLocationSshToolTest.java
@@ -25,7 +25,10 @@ import java.util.Map;
 
 import org.apache.brooklyn.api.location.LocationSpec;
 import org.apache.brooklyn.api.location.MachineProvisioningLocation;
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
 import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.apache.brooklyn.test.Asserts;
 import org.apache.brooklyn.util.core.internal.ssh.RecordingSshTool;
 import org.apache.brooklyn.util.core.internal.ssh.RecordingSshTool.ExecCmd;
 import org.apache.brooklyn.util.core.internal.ssh.SshTool;
@@ -43,7 +46,8 @@ import com.google.common.collect.Iterables;
  */
 public class SshMachineLocationSshToolTest extends BrooklynAppUnitTestSupport {
 
-    // TODO See SshEffectorTasks.getSshFlags, called by AbstractSoftwareProcessSshDriver.getSshFlags.
+    // Also see SshEffectorTasks.getSshFlags, called by AbstractSoftwareProcessSshDriver.getSshFlags.
+    //
     // That retrieves all the mgmt.config, entity.config and location.config to search for ssh-related
     // configuration options. If you *just* instantiate the location directly, then it doesn't get the
     // mgmt.config options.
@@ -72,7 +76,24 @@ public class SshMachineLocationSshToolTest extends BrooklynAppUnitTestSupport {
                 .configure(SshMachineLocation.SSH_TOOL_CLASS, RecordingSshTool.class.getName()));
         runCustomSshToolClass(machine);
     }
-    
+
+    @Test
+    public void testCustomSshToolClassNotViaBrooklynPropertiesUnlessEffector() throws Exception {
+        ((ManagementContextInternal)mgmt).getBrooklynProperties().put(BrooklynConfigKeys.SSH_TOOL_CLASS, RecordingSshTool.class.getName());
+        SshMachineLocation machine = mgmt.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+                .configure("address", "localhost"));
+        // fails because the machine doesn't get the properties; it is the SshEffectorTasks which does it
+        // see also EntitySshToolTest
+        Asserts.assertFails(() -> { runCustomSshToolClass(machine); return null; });
+    }
+
+    @Test
+    public void testCustomSshToolClassNotSet() throws Exception {
+        SshMachineLocation machine = mgmt.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+                .configure("address", "localhost"));
+        Asserts.assertFails(() -> { runCustomSshToolClass(machine); return null; });
+    }
+
     @Test
     @SuppressWarnings("deprecation")
     public void testCustomSshToolClassUsingLegacy() throws Exception {
diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/provider/CarrenzaLocationLiveTest.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/provider/CarrenzaLocationLiveTest.java
index 32b7984843..41a178e888 100644
--- a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/provider/CarrenzaLocationLiveTest.java
+++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/provider/CarrenzaLocationLiveTest.java
@@ -27,6 +27,7 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.brooklyn.api.location.NoMachinesAvailableException;
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
 import org.apache.brooklyn.core.internal.BrooklynProperties;
 import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
 import org.apache.brooklyn.location.jclouds.JcloudsLocation;
@@ -76,7 +77,7 @@ class CarrenzaLocationLiveTest {
         brooklynProperties.remove("brooklyn.jclouds."+PROVIDER+".hardware-id");
 
         // Also removes scriptHeader (e.g. if doing `. ~/.bashrc` and `. ~/.profile`, then that can cause "stdin: is not a tty")
-        brooklynProperties.remove("brooklyn.ssh.config.scriptHeader");
+        brooklynProperties.remove(BrooklynConfigKeys.SSH_CONFIG_SCRIPT_HEADER.getName());
         
         brooklynProperties.put("brooklyn.jclouds."+PROVIDER+".jclouds.endpoint", ENDPOINT);
         brooklynProperties.put("brooklyn.jclouds."+PROVIDER+".imageId", WINDOWS_IMAGE_ID);
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/AbstractGoogleComputeLiveTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/AbstractGoogleComputeLiveTest.java
index 7d70dd0672..edef81846f 100644
--- a/software/base/src/test/java/org/apache/brooklyn/entity/AbstractGoogleComputeLiveTest.java
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/AbstractGoogleComputeLiveTest.java
@@ -22,6 +22,7 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
 import org.apache.brooklyn.core.internal.BrooklynProperties;
 import org.apache.brooklyn.core.test.BrooklynAppLiveTestSupport;
 import org.apache.brooklyn.util.collections.MutableMap;
@@ -61,7 +62,7 @@ public abstract class AbstractGoogleComputeLiveTest extends BrooklynAppLiveTestS
         }
 
         // Also removes scriptHeader (e.g. if doing `. ~/.bashrc` and `. ~/.profile`, then that can cause "stdin: is not a tty")
-        result.remove("brooklyn.ssh.config.scriptHeader");
+        result.remove(BrooklynConfigKeys.SSH_CONFIG_SCRIPT_HEADER.getName());
         
         return result;
     }
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/AbstractMultiDistroLiveTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/AbstractMultiDistroLiveTest.java
index 38a6cb37b7..66f2485ec1 100644
--- a/software/base/src/test/java/org/apache/brooklyn/entity/AbstractMultiDistroLiveTest.java
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/AbstractMultiDistroLiveTest.java
@@ -25,6 +25,7 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
 import org.apache.brooklyn.core.internal.BrooklynProperties;
 import org.apache.brooklyn.core.location.Machines;
 import org.apache.brooklyn.core.test.BrooklynAppLiveTestSupport;
@@ -92,7 +93,7 @@ public abstract class AbstractMultiDistroLiveTest extends BrooklynAppLiveTestSup
         }
 
         // Also removes scriptHeader (e.g. if doing `. ~/.bashrc` and `. ~/.profile`, then that can cause "stdin: is not a tty")
-        brooklynProperties.remove("brooklyn.ssh.config.scriptHeader");
+        brooklynProperties.remove(BrooklynConfigKeys.SSH_CONFIG_SCRIPT_HEADER.getName());
         
         LocalManagementContextForTests localManagementContextForTests = new LocalManagementContextForTests(brooklynProperties);
         localManagementContextForTests.generateManagementPlaneId();
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/AbstractSoftlayerLiveTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/AbstractSoftlayerLiveTest.java
index f7e7e0703b..ee487800e6 100644
--- a/software/base/src/test/java/org/apache/brooklyn/entity/AbstractSoftlayerLiveTest.java
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/AbstractSoftlayerLiveTest.java
@@ -22,6 +22,7 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
 import org.apache.brooklyn.core.internal.BrooklynProperties;
 import org.apache.brooklyn.core.test.BrooklynAppLiveTestSupport;
 import org.apache.brooklyn.util.collections.MutableMap;
@@ -60,7 +61,7 @@ public abstract class AbstractSoftlayerLiveTest extends BrooklynAppLiveTestSuppo
         }
 
         // Also removes scriptHeader (e.g. if doing `. ~/.bashrc` and `. ~/.profile`, then that can cause "stdin: is not a tty")
-        result.remove("brooklyn.ssh.config.scriptHeader");
+        result.remove(BrooklynConfigKeys.SSH_CONFIG_SCRIPT_HEADER.getName());
         
         return result;
     }
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizerLiveTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizerLiveTest.java
index 343814eb21..eb2a1be845 100644
--- a/software/base/src/test/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizerLiveTest.java
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizerLiveTest.java
@@ -75,7 +75,7 @@ public class SetHostnameCustomizerLiveTest extends BrooklynAppLiveTestSupport {
         }
 
         // Also removes scriptHeader (e.g. if doing `. ~/.bashrc` and `. ~/.profile`, then that can cause "stdin: is not a tty")
-        brooklynProperties.remove("brooklyn.ssh.config.scriptHeader");
+        brooklynProperties.remove(BrooklynConfigKeys.SSH_CONFIG_SCRIPT_HEADER.getName());
         
         mgmt = new LocalManagementContext(brooklynProperties);
         
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolLiveTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolLiveTest.java
index 60b3b0fe6a..187cc9adf3 100644
--- a/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolLiveTest.java
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/machine/pool/ServerPoolLiveTest.java
@@ -26,6 +26,7 @@ import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.location.Location;
 import org.apache.brooklyn.api.mgmt.ManagementContext;
 import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
 import org.apache.brooklyn.core.internal.BrooklynProperties;
 import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
 import org.apache.brooklyn.core.test.entity.TestApplication;
@@ -69,7 +70,7 @@ public class ServerPoolLiveTest extends AbstractServerPoolTest {
         }
 
         // Also removes scriptHeader (e.g. if doing `. ~/.bashrc` and `. ~/.profile`, then that can cause "stdin: is not a tty")
-        brooklynProperties.remove("brooklyn.ssh.config.scriptHeader");
+        brooklynProperties.remove(BrooklynConfigKeys.SSH_CONFIG_SCRIPT_HEADER.getName());
         return new LocalManagementContextForTests(brooklynProperties);
     }
 
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/AbstractDockerLiveTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/AbstractDockerLiveTest.java
index c2f626dff5..72a26a5d13 100644
--- a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/AbstractDockerLiveTest.java
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/AbstractDockerLiveTest.java
@@ -23,6 +23,7 @@ import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 
 import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
 import org.apache.brooklyn.core.internal.BrooklynProperties;
 import org.apache.brooklyn.core.test.BrooklynAppLiveTestSupport;
 import org.apache.brooklyn.util.collections.MutableMap;
@@ -57,7 +58,7 @@ public abstract class AbstractDockerLiveTest extends BrooklynAppLiveTestSupport
         }
 
         // Also removes scriptHeader (e.g. if doing `. ~/.bashrc` and `. ~/.profile`, then that can cause "stdin: is not a tty")
-        result.remove("brooklyn.ssh.config.scriptHeader");
+        result.remove(BrooklynConfigKeys.SSH_CONFIG_SCRIPT_HEADER.getName());
 
         return result;
     }
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareProcessStopsDuringStartJcloudsLiveTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareProcessStopsDuringStartJcloudsLiveTest.java
index 50ad53b86b..7839de26bd 100644
--- a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareProcessStopsDuringStartJcloudsLiveTest.java
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareProcessStopsDuringStartJcloudsLiveTest.java
@@ -95,7 +95,7 @@ public class SoftwareProcessStopsDuringStartJcloudsLiveTest extends BrooklynAppL
         brooklynProperties = BrooklynProperties.Factory.newDefault();
 
         // Also removes scriptHeader (e.g. if doing `. ~/.bashrc` and `. ~/.profile`, then that can cause "stdin: is not a tty")
-        brooklynProperties.remove("brooklyn.ssh.config.scriptHeader");
+        brooklynProperties.remove(BrooklynConfigKeys.SSH_CONFIG_SCRIPT_HEADER.getName());
 
         mgmt = new LocalManagementContextForTests(brooklynProperties);
         super.setUp();
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/SecurityGroupLiveTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/SecurityGroupLiveTest.java
index 12a5a30e80..4405e99efe 100644
--- a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/SecurityGroupLiveTest.java
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/SecurityGroupLiveTest.java
@@ -25,6 +25,7 @@ import com.google.common.collect.ImmutableSet;
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.entity.EntitySpec;
 import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
 import org.apache.brooklyn.core.entity.EntityAsserts;
 import org.apache.brooklyn.core.entity.trait.Startable;
 import org.apache.brooklyn.core.internal.BrooklynProperties;
@@ -88,7 +89,7 @@ public class SecurityGroupLiveTest extends BrooklynAppLiveTestSupport  {
         brooklynProperties.remove("brooklyn.jclouds."+PROVIDER+".hardware-id");
 
         // Also removes scriptHeader (e.g. if doing `. ~/.bashrc` and `. ~/.profile`, then that can cause "stdin: is not a tty")
-        brooklynProperties.remove("brooklyn.ssh.config.scriptHeader");
+        brooklynProperties.remove(BrooklynConfigKeys.SSH_CONFIG_SCRIPT_HEADER.getName());
 
         mgmt = new LocalManagementContextForTests(brooklynProperties);
 


[brooklyn-server] 01/06: refactor BashCommands to have non-static, context sensitive evaluation

Posted by he...@apache.org.
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 b81787c28be0f37ddd57e450eaed1d13710702b2
Author: Alex Heneveld <al...@cloudsoft.io>
AuthorDate: Fri Jul 29 02:08:40 2022 +0100

    refactor BashCommands to have non-static, context sensitive evaluation
    
    in particular allowing whether to ignoreCerts to be configurable
    via mgmt properties or entity config
---
 .../util/core/file/BrooklynOsCommands.java         |   52 +
 .../org/apache/brooklyn/util/ssh/BashCommands.java | 1070 +++++---------------
 ...Commands.java => BashCommandsConfigurable.java} |  234 ++---
 .../apache/brooklyn/util/ssh/IptablesCommands.java |  142 +--
 ...ands.java => IptablesCommandsConfigurable.java} |   75 +-
 5 files changed, 527 insertions(+), 1046 deletions(-)

diff --git a/core/src/main/java/org/apache/brooklyn/util/core/file/BrooklynOsCommands.java b/core/src/main/java/org/apache/brooklyn/util/core/file/BrooklynOsCommands.java
new file mode 100644
index 0000000000..54ecd2a9f7
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/util/core/file/BrooklynOsCommands.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.util.core.file;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
+import org.apache.brooklyn.util.ssh.IptablesCommandsConfigurable;
+
+public class BrooklynOsCommands {
+
+    public static final ConfigKey<Boolean> OS_COMMANDS_IGNORE_CERTS = ConfigKeys.newBooleanConfigKey("brooklyn.os.commands.ignoreCerts", "Whether to generate OS commands that ignore certs, e.g. curl -k");
+
+    public static BashCommandsConfigurable bash(ManagementContext mgmt) {
+        return BashCommandsConfigurable.newInstance().withIgnoreCerts( ((ManagementContextInternal)mgmt).getBrooklynProperties().getConfig(OS_COMMANDS_IGNORE_CERTS) );
+    }
+
+    public static IptablesCommandsConfigurable bashIptables(ManagementContext mgmt) {
+        return new IptablesCommandsConfigurable(bash(mgmt));
+    }
+
+    public static BashCommandsConfigurable bash(Entity entity) {
+        Boolean ignoreCerts = entity.config().get(OS_COMMANDS_IGNORE_CERTS);
+        if (ignoreCerts!=null) return BashCommandsConfigurable.newInstance().withIgnoreCerts(ignoreCerts);
+        return bash( ((EntityInternal)entity).getManagementContext() );
+    }
+
+    public static IptablesCommandsConfigurable bashIptables(Entity entity) {
+        return new IptablesCommandsConfigurable(bash(entity));
+    }
+
+}
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/ssh/BashCommands.java b/utils/common/src/main/java/org/apache/brooklyn/util/ssh/BashCommands.java
index d60c9a9a35..4bd36ba31a 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/util/ssh/BashCommands.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/ssh/BashCommands.java
@@ -18,812 +18,284 @@
  */
 package org.apache.brooklyn.util.ssh;
 
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static java.lang.String.format;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.brooklyn.util.collections.MutableMap;
-import org.apache.brooklyn.util.text.Identifiers;
-import org.apache.brooklyn.util.text.Strings;
-import org.apache.brooklyn.util.text.StringEscapes.BashStringEscapes;
-import org.apache.brooklyn.util.time.Duration;
-
 import com.google.common.annotations.Beta;
 import com.google.common.base.Joiner;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.text.Identifiers;
+import org.apache.brooklyn.util.text.StringEscapes.BashStringEscapes;
+import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.time.Duration;
+
+import java.util.*;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.lang.String.format;
 
 public class BashCommands {
 
-    /**
-     * Returns a string for checking whether the given executable is available,
-     * and installing it if necessary.
-     * <p/>
-     * Uses {@link #installPackage} and accepts the same flags e.g. for apt, yum, rpm.
-     */
-    public static String installExecutable(Map<?,?> flags, String executable) {
-        return onlyIfExecutableMissing(executable, installPackage(flags, executable));
-    }
-
-    public static String installExecutable(String executable) {
-        return installExecutable(MutableMap.of(), executable);
-    }
-
-    /**
-     * Returns a command with all output redirected to /dev/null
-     */
-    public static String quiet(String command) {
-        return format("(%s > /dev/null 2>&1)", command);
-    }
-
-    /**
-     * Returns a command that always exits successfully
-     */
-    public static String ok(String command) {
-        return String.format("(%s || true)", command);
-    }
-
-    /**
-     * Returns a command for safely running as root, using {@code sudo}.
-     * <p/>
-     * Ensuring non-blocking if password not set by using 
-     * {@code -n} which means to exit if password required
-     * (this is unsupported in Ubuntu 8 but all modern OS's seem okay with this!),
-     * and (perhaps unnecessarily ?)
-     * {@code -S} which reads from stdin (routed to {@code /dev/null}, it was claimed here previously, though I'm not sure?).
-     * <p/>
-     * Also specify {@code -E} to pass the parent environment in.
-     * <p/> 
-     * If already root, simply runs the command, wrapped in brackets in case it is backgrounded.
-     * <p/>
-     * The command is not quoted or escaped in any ways. 
-     * If you are doing privileged redirect you may need to pass e.g. "bash -c 'echo hi > file'".
-     * <p/>
-     * If null is supplied, it is returned (sometimes used to indicate no command desired).
-     */
-    public static String sudo(String command) {
-        if (command==null) return null;
-        if (command.startsWith("( ") || command.endsWith(" &"))
-            return sudoNew(command);
-        else
-            return sudoOld(command);
-    }
-
-    public static String authSudo(String command, String password) {
-        checkNotNull(password, "password must not be null");
-        if (command==null) return null;
-        if (command.startsWith("( ") || command.endsWith(" &")) {
-            throw new UnsupportedOperationException("authSudo supports only simple commands, not those wrapped in parentheses or backgrounded: cmd="+command);
-        }
-        // some OS's (which?) fail if you try running sudo when you're already root (dumb but true)
-        return format("( if test \"$UID\" -eq 0; then ( %s ); else echo -e '%s\\n' | sudo -E -S -- %s; fi )", command, password, command);
-    }
-
-    // TODO would like to move away from sudoOld -- but needs extensive testing!
-    
-    private static String sudoOld(String command) {
-        if (command==null) return null;
-        // some OS's (which?) fail if you try running sudo when you're already root (dumb but true)
-        return format("( if test \"$UID\" -eq 0; then ( %s ); else sudo -E -n -S -- %s; fi )", command, command);
-    }
-    private static String sudoNew(String command) {
-        if (command==null) return null;
-        // on some OS's e.g. Centos 6.5 in SL, sudo -- X tries to run X as a literal argument;
-        // in particular "( echo foo && echo bar )" fails when passed as an argument in that way,
-        // but works if passed to bash -c;
-        // but others e.g. OS X fail if you say  sudo -- bash -c "( echo foo )"   ... not liking the parentheses
-        // piping to sudo bash seems the most reliable way
-        return "( if test \"$UID\" -eq 0; then ( "+command+" ); else "
-                + "echo " + BashStringEscapes.wrapBash(command) + " | "
-                + "sudo -E -n -S -s -- bash"
-                + " ; fi )";
-    }
-
-    /** sudo to a given user and run the indicated command;
-     * @deprecated since 0.7.0 semantics of this are fiddly, e.g. whether user gets their environment */
-    @Deprecated
-    @Beta
-    public static String sudoAsUser(String user, String command) {
-        return sudoAsUserOld(user, command);
-    }
-    
-    private static String sudoAsUserOld(String user, String command) {
-        if (command == null) return null;
-        return format("{ sudo -E -n -u %s -s -- %s ; }", user, command);
-    }
-    // TODO would like to move away from sudoOld -- but needs extensive testing!
-//    private static String sudoAsUserNew(String user, String command) {
-//        if (command == null) return null;
-//          // no -E, run with permissions of this user
-//          // FIXME still doesn't always work e.g. doesn't have path of user 
-//          // (Alex says: can't find any combinations which work reliably)
-//        return "{ sudo -n -S -i -u "+user+" -- "+BashStringEscapes.wrapBash(command)+" ; }";
-//    }
-
-    public static String addSbinPathCommand() {
-        return "export PATH=" + sbinPath();
-    }
-
-    public static String sbinPath() {
-        return "$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
-    }
-
-    /** executes a command, then as user tees the output to the given file. 
-     * useful e.g. for appending to a file which is only writable by root or a priveleged user. */
-    public static String executeCommandThenAsUserTeeOutputToFile(String commandWhoseOutputToWrite, String user, String file) {
-        return format("{ %s | sudo -E -n -u %s -s -- tee -a %s ; }",
-                commandWhoseOutputToWrite, user, file);
-    }
-
-    /**
-     * Some machines require a TTY for sudo. Brooklyn by default does not use a TTY
-     * so that it can get separate STDERR and STDOUT streams. You can enable a TTY as an
-     * option to every SSH command, or you can do it once and modify the machine so that
-     * a TTY is not subsequently required. If this task has already been executed it
-     * will try to detect the changes and do nothing.
-     * <p>
-     * This command must be run with allocatePTY set as a flag to ssh.
-     * See {@link SshTasks#dontRequireTtyForSudo(SshMachineLocation, OnFailingTask)} which sets that up. 
-     * <p>
-     * Having a TTY for sudo seems like another case of imaginary security which is just irritating.
-     * Like water restrictions at airport security.
-     */
-    public static String dontRequireTtyForSudo() {
-        String sudoersFileName =  "/etc/sudoers";
-        String tmpSuffix = Identifiers.makeRandomLowercaseId(6); // Avoid clobbering
-
-        // Visudo's quiet mode (-q) is not enabled. visudo's output is used for diagnostic purposes 
-        return ifFileExistsElse0(sudoersFileName, 
-                alternatives(
-                    sudo(format("grep brooklyn-removed-require-tty %s", sudoersFileName)),
-                    chainGroup(
-                        sudo(format("cp %1$s %1$s.%2$s", sudoersFileName, tmpSuffix)),
-                        sudo(format("sed -i.brooklyn.bak 's/.*requiretty.*/#brooklyn-removed-require-tty/' %1$s.%2$s", sudoersFileName, tmpSuffix)),
-                        sudo(format("visudo -c -f %1$s.%2$s", sudoersFileName, tmpSuffix)),
-                        sudo(format("mv %1$s.%2$s %1$s", sudoersFileName, tmpSuffix)))));
-    }
-
-    /** generates ~/.ssh/id_rsa if that file does not exist */
-    public static String generateKeyInDotSshIdRsaIfNotThere() {
-        return "[ -f ~/.ssh/id_rsa ] || ( mkdir -p ~/.ssh ; chmod 700 ~/.ssh ; ssh-keygen -t rsa -N '' -f ~/.ssh/id_rsa )";
-    }
-    
-    // TODO a builder would be better than these ifBlahExistsElseBlah methods!
-    // (ideally formatting better also; though maybe SshTasks would be better?)
-    
-    /**
-     * Returns a command that runs only if the specified file (or link or directory) exists;
-     * if the command runs and fails that exit is preserved (but if the file does not exist exit code is zero).
-     * Executed as  { { not-file-exists && ok ; } || command ; }  for portability.
-     * ("if [ ... ] ; then xxx ; else xxx ; fi" syntax is not quite as portable, I seem to recall (not sure, Alex Aug 2013).) 
-     */
-    public static String ifFileExistsElse0(String path, String command) {
-        return alternativesGroup(
-                chainGroup(format("test ! -e %s", path), "true"),
-                command);
-    }
-    /** as {@link #ifFileExistsElse0(String, String)} but returns non-zero if the test fails (also returns non-zero if the command fails,
-     * so you can't tell the difference :( -- we need if ; then ; else ; fi semantics for that I think, but not sure how portable that is) */
-    public static String ifFileExistsElse1(String path, String command) {
-        return chainGroup(format("test -e %s", path), command);
-    }
-
-    /**
-     * Returns a command that runs only if the specified executable exists on the path (using `which`).
-     * if the command runs and fails that exit is preserved (but if the executable is not on the path exit code is zero).
-     * @see #ifFileExistsElse0(String, String) for implementation discussion, using <code>{ { test -z `which executable` && true ; } || command ; } 
-     */
-    public static String ifExecutableElse0(String executable, String command) {
-        return alternativesGroup(
-                chainGroup(format("test -z `which %s`", executable), "true"),
-                command);
-    }
-
-    /** as {@link #ifExecutableElse0(String, String)} but returns 1 if the test fails (also returns non-zero if the command fails) */
-    public static String ifExecutableElse1(String executable, String command) {
-        return chainGroup(format("which %s", executable), command);
-    }
-
-    /**
-     * Returns a command which
-     * executes <code>statement</code> only if <code>command</code> is NOT found in <code>$PATH</code>
-     *
-     * @param command
-     * @param statement
-     * @return command
-     */
-    public static String ifNotExecutable(String command, String statement) {
-        return String.format("{ { test ! -z `which %s`; } || { %s; } }", command, statement);
-    }
-
-    /**
-     * Returns a command that runs only if the specified executable exists on the path (using `which`).
-     * if the command runs and fails that exit is preserved (but if the executable is not on the path exit code is zero).
-     * @see #ifFileExistsElse0(String, String) for implementation discussion, using <code>{ { test -z `which executable` && true ; } || command ; }
-     */
-    public static String onlyIfExecutableMissing(String executable, String command) {
-        return alternativesGroup(format("which %s", executable), command);
-    }
-
-    /**
-     * @deprecated As of release 0.10.0, replaced by {@link #ifExecutableDoesNotExistElse(String, String, String)}
-     */
-    @Deprecated
-    public static String ifExecutableElse(String command, String ifNotExist, String ifExist) {
-        return ifExecutableDoesNotExistElse(command, ifNotExist, ifExist);
-    }
-
-    public static String ifExecutableDoesNotExistElse(String command, String ifNotExist, String ifExist) {
-        return com.google.common.base.Joiner.on('\n').join(
-                ifExecutableDoesNotExistElse(command, ImmutableList.<String>of(ifNotExist), ImmutableList.<String>of(ifExist)));
-    }
-
-    /**
-     * @deprecated  As of release 0.10.0, replaced by {@link #ifExecutableDoesNotExistElse(String, List, List)}
-     */
-    @Deprecated
-    public static ImmutableList<String> ifExecutableElse(String command, List<String> ifNotExist, List<String> ifExist) {
-        return ifExecutableDoesNotExistElse(command, ifNotExist, ifExist);
-    }
-
-    public static ImmutableList<String> ifExecutableDoesNotExistElse(String command, List<String> ifNotExist, List<String> ifExist) {
-        return ImmutableList.<String>builder()
-                .add(String.format("if test -z `which %s`; then", command))
-                .addAll(ifNotExist)
-                .add("else")
-                .addAll(ifExist)
-                .add("fi")
-                .build();
-    }
-
-    /**
-     * Returns a sequence of chained commands that runs until one of them fails (i.e. joined by '&&')
-     * This currently runs as a subshell (so exits are swallowed) but behaviour may be changed imminently. 
-     * (Use {@link #chainGroup(Collection)} or {@link #chainSubshell(Collection)} to be clear.)
-     */
-    public static String chain(Collection<String> commands) {
-        return "( " + Strings.join(commands, " && ") + " )";
-    }
-
-    /** Convenience for {@link #chain(Collection)} */
-    public static String chain(String ...commands) {
-        return "( " + Strings.join(commands, " && ") + " )";
-    }
-
-    /** As {@link #chain(Collection)}, but explicitly using { } grouping characters
-     * to ensure exits are propagated. */
-    public static String chainGroup(Collection<String> commands) {
-        // spaces required around curly braces
-        return "{ " + Strings.join(commands, " && ") + " ; }";
-    }
-
-    /** As {@link #chainGroup(Collection)} */
-    public static String chainGroup(String ...commands) {
-        return "{ " + Strings.join(commands, " && ") + " ; }";
-    }
-
-    /** As {@link #chain(Collection)}, but explicitly using ( ) grouping characters
-     * to ensure exits are caught. */
-    public static String chainSubshell(Collection<String> commands) {
-        // the spaces are not required, but it might be possible that a (( expr )) is interpreted differently
-        // (won't hurt to have the spaces in any case!) 
-        return "( " + Strings.join(commands, " && ") + " )";
-    }
-
-    /** As {@link #chainSubshell(Collection)} */
-    public static String chainSubshell(String ...commands) {
-        return "( " + Strings.join(commands, " && ") + "  )";
-    }
-
-    /**
-     * Returns a sequence of chained commands that runs until one of them succeeds (i.e. joined by '||').
-     * This currently runs as a subshell (so exits are swallowed) but behaviour may be changed imminently. 
-     * (Use {@link #alternativesGroup(Collection)} or {@link #alternativesSubshell(Collection)} to be clear.)
-     */
-    public static String alternatives(Collection<String> commands) {
-        return "( " + Strings.join(commands, " || ") + " )";
-    }
-
-    /** As {@link #alternatives(Collection)} */
-    public static String alternatives(String ...commands) {
-        return "( " + Strings.join(commands, " || ") + " )";
-    }
-
-    /** As {@link #alternatives(Collection)}, but explicitly using { } grouping characters
-     * to ensure exits are propagated. */
-    public static String alternativesGroup(Collection<String> commands) {
-        // spaces required around curly braces
-        return "{ " + Strings.join(commands, " || ") + " ; }";
-    }
-
-    /** As {@link #alternativesGroup(Collection)} */
-    public static String alternativesGroup(String ...commands) {
-        return "{ " + Strings.join(commands, " || ") + " ; }";
-    }
-
-    /** As {@link #alternatives(Collection)}, but explicitly using ( ) grouping characters
-     * to ensure exits are caught. */
-    public static String alternativesSubshell(Collection<String> commands) {
-        // the spaces are not required, but it might be possible that a (( expr )) is interpreted differently
-        // (won't hurt to have the spaces in any case!) 
-        return "( " + Strings.join(commands, " || ") + " )";
-    }
-
-    /** As {@link #alternativesSubshell(Collection)} */
-    public static String alternativesSubshell(String ...commands) {
-        return "( " + Strings.join(commands, " || ") + "  )";
-    }
-
-    /** returns the pattern formatted with the given arg if the arg is not null, otherwise returns null */
-    public static String formatIfNotNull(String pattern, Object arg) {
-        if (arg==null) return null;
-        return format(pattern, arg);
-    }
-    
-    public static String installPackage(String packageDefaultName) {
-        return installPackage(MutableMap.of(), packageDefaultName);
-    }
-    /**
-     * Returns a command for installing the given package.
-     * <p>
-     * Warns, but does not fail or return non-zero if it ultimately fails.
-     * <p>
-     * Flags can contain common overrides for {@code apt}, {@code yum}, {@code port} and {@code brew}
-     * as the package names can be different for each of those. Setting the default package name to
-     * {@literal null} will use only the overridden package manager values. The {@code onlyifmissing} flag
-     * adds a check for an executable, and only attempts to install packages if it is not found.
-     * <pre>
-     * installPackage(ImmutableMap.of("yum", "openssl-devel", "apt", "openssl libssl-dev zlib1g-dev"), "libssl-devel");
-     * installPackage(ImmutableMap.of("apt", "libaio1"), null);
-     * installPackage(ImmutableMap.of("onlyifmissing", "curl"), "curl");
-     * </pre>
-     */
-    public static String installPackage(Map<?,?> flags, String packageDefaultName) {
-        return installPackageOr(flags, packageDefaultName, null);
-    }
-    public static String installPackageOrFail(Map<?,?> flags, String packageDefaultName) {
-        return installPackageOr(flags, packageDefaultName, "exit 9");
-    }
-    public static String installPackageOr(Map<?,?> flags, String packageDefaultName, String optionalCommandToRunIfNone) {
-        String ifMissing = (String) flags.get("onlyifmissing");
-        String zypperInstall = formatIfNotNull("zypper --non-interactive --no-gpg-checks install %s", getFlag(flags, "zypper", packageDefaultName));
-        String aptInstall = formatIfNotNull("apt-get install -y --allow-unauthenticated %s", getFlag(flags, "apt", packageDefaultName));
-        String yumInstall = formatIfNotNull("yum -y --nogpgcheck install %s", getFlag(flags, "yum", packageDefaultName));
-        String brewInstall = formatIfNotNull("brew install %s", getFlag(flags, "brew", packageDefaultName));
-        String portInstall = formatIfNotNull("port install %s", getFlag(flags, "port", packageDefaultName));
-
-        List<String> commands = new LinkedList<String>();
-        if (ifMissing != null)
-            commands.add(format("which %s", ifMissing));
-        if (zypperInstall != null)
-            commands.add(ifExecutableElse1("zypper",
-                    chainGroup(
-                            "echo zypper exists, doing refresh",
-                            ok(sudo("zypper --non-interactive --no-gpg-checks refresh")),
-                            sudo(zypperInstall))));
-        if (aptInstall != null)
-            commands.add(ifExecutableElse1("apt-get",
-                    chainGroup(
-                        "echo apt-get exists, doing update",
-                        "export DEBIAN_FRONTEND=noninteractive",
-                        ok(sudo("apt-get update")), 
-                        sudo(aptInstall))));
-        if (yumInstall != null)
-            // Need to upgrade ca-certificates sometimes:
-            // http://serverfault.com/questions/637549/epel-repo-for-centos-6-causing-error?newreg=7c6019c0d0ae483c8bb3af387166ce49
-            commands.add(ifExecutableElse1("yum", 
-                    chainGroup(
-                        "echo yum exists, doing update",
-                        ok(sudo("yum check-update")),
-                        ok(sudo("yum -y install epel-release")),
-                        ok(sudo("yum upgrade -y ca-certificates --disablerepo=epel")),
-                        sudo(yumInstall))));
-        if (brewInstall != null)
-            commands.add(ifExecutableElse1("brew", brewInstall));
-        if (portInstall != null)
-            commands.add(ifExecutableElse1("port", sudo(portInstall)));
-
-        String lastCommand = ok(warn("WARNING: no known/successful package manager to install " +
-                (packageDefaultName!=null ? packageDefaultName : flags.toString()) +
-                ", may fail subsequently"));
-        if (optionalCommandToRunIfNone != null)
-            lastCommand = chain(lastCommand, optionalCommandToRunIfNone);
-        commands.add(lastCommand);
-        
-        return alternatives(commands);
-    }
-    
-    public static String warn(String message) {
-        return "( echo "+BashStringEscapes.wrapBash(message)+" | tee /dev/stderr )";
-    }
-
-    /** returns a command which logs a message to stdout and stderr then exits with the given error code */
-    public static String fail(String message, int code) {
-        return chainGroup(warn(message), "exit "+code);
-    }
-
-    /** requires the command to have a non-zero exit code; e.g.
-     * <code>require("which foo", "Command foo must be found", 1)</code> */
-    public static String require(String command, String failureMessage, int exitCode) {
-        return alternativesGroup(command, fail(failureMessage, exitCode));
-    }
-
-    /** as {@link #require(String, String, int)} but returning the original exit code */
-    public static String require(String command, String failureMessage) {
-        return alternativesGroup(command, chainGroup("EXIT_CODE=$?", warn(failureMessage), "exit $EXIT_CODE"));
-    }
-
-    /** requires the test to pass, as valid bash `test` arguments; e.g.
-     * <code>requireTest("-f /etc/hosts", "Hosts file must exist", 1)</code> */
-    public static String requireTest(String test, String failureMessage, int exitCode) {
-        return require("test "+test, failureMessage, exitCode);
-    }
-
-    /** as {@link #requireTest(String, String, int)} but returning the original exit code */
-    public static String requireTest(String test, String failureMessage) {
-        return require("test "+test, failureMessage);
-    }
-
-    /** fails with nice error if the given file does not exist */
-    public static String requireFile(String file) {
-        return requireTest("-f "+BashStringEscapes.wrapBash(file), "The required file \""+file+"\" does not exist");
-    }
-
-    /** fails with nice error if the given file does not exist */
-    public static String requireExecutable(String command) {
-        return require("which "+BashStringEscapes.wrapBash(command), "The required executable \""+command+"\" does not exist");
-    }
-
-    public static String waitForFileContents(String file, String desiredContent, Duration timeout, boolean failOnTimeout) {
-        long secs = Math.max(timeout.toSeconds(), 1);
-        
-        List<String> commands = ImmutableList.of(
-                "for i in {1.."+secs+"}; do",
-                "    grep '"+desiredContent+"' "+file+" && result=0 || result=$?",
-                "    [ \"$result\" == 0 ] && break",
-                "    sleep 1",
-                "done",
-                "if test \"$result\" -ne 0; then",
-                "    ls -l "+file+" || true",
-                "    cat "+file+" || true",
-                "    "+ (failOnTimeout ?
-                            "echo \"Couldn't find "+desiredContent+" in "+file+"; aborting\" && exit 1" :
-                            "echo \"Couldn't find "+desiredContent+" in "+file+"; continuing\""),
-                "fi");
-        return Joiner.on("\n").join(commands);
-    }
-
-    public static String waitForFileExists(String file, Duration timeout, boolean failOnTimeout) {
-        long secs = Math.max(timeout.toSeconds(), 1);
-        
-        List<String> commands = ImmutableList.of(
-                "for i in {1.."+secs+"}; do",
-                "    [[ -f "+file+" ]] && result=0 || result=$?",
-                "    [ \"$result\" == 0 ] && break",
-                "    sleep 1",
-                "done",
-                "if test \"$result\" -ne 0; then",
-                "    "+ (failOnTimeout ?
-                            "echo \"Couldn't find file "+file+"; aborting\" && exit 1" :
-                            "echo \"Couldn't find file "+file+"; continuing\""),
-                "fi");
-        return Joiner.on("\n").join(commands);
-    }
-
-    public static String waitForPortFree(int port, Duration timeout, boolean failOnTimeout) {
-        long secs = Math.max(timeout.toSeconds(), 1);
-
-        // TODO How platform-dependent are the args + output format of netstat?
-        // TODO Not using sudo as wrapping either netstat call or sudo(alternativesGroup(...)) fails; parentheses too confusing!
-        String netstatCommand = alternativesGroup(
-                "sudo netstat -antp --tcp", // for Centos
-                "sudo netstat -antp TCP"); // for OS X 
-        
-        // number could appear in an IP address or as a port; look for white space at end, and dot or colon before
-        String grepCommand = "grep -E '(:|\\.)"+port+"($|\\s)' > /dev/null";
-        
-        List<String> commands = ImmutableList.of(
-                "for i in {1.."+secs+"}; do",
-                "    "+BashCommands.requireExecutable("netstat"),
-                "    "+alternativesGroup(
-                        chainGroup("which awk", "AWK_EXEC=awk"), 
-                        chainGroup("which gawk", "AWK_EXEC=gawk"), 
-                        chainGroup("which /usr/bin/awk", "AWK_EXEC=/usr/bin/awk"), 
-                        chainGroup("echo \"No awk to determine if Port "+port+" still in use; aborting\"", "exit 1")),
-                "    "+netstatCommand+" | $AWK_EXEC '{print $4}' | "+grepCommand+" && result=0 || result=$?",
-                "    [ \"$result\" != 0 ] && break",
-                "    sleep 1",
-                "done",
-                "if test \"$result\" -eq 0; then",
-                "    "+ (failOnTimeout ?
-                            "echo \"Port "+port+" still in use (according to netstat); aborting\" && exit 1" :
-                            "echo \"Port "+port+" still in use (according to netstat); continuing\""),
-                "fi");
-        return Joiner.on("\n").join(commands);
-    }
-
-    public static String unzip(String file, String targetDir) {
-        return "unzip " + file + (Strings.isNonBlank(targetDir) ? " -d "+targetDir : "");
-    }
-
-    public static final String INSTALL_TAR = installExecutable("tar");
-    public static final String INSTALL_CURL = installExecutable("curl");
-    public static final String INSTALL_WGET = installExecutable("wget");
-    public static final String INSTALL_ZIP = installExecutable("zip");
-    public static final String INSTALL_UNZIP = alternatives(installExecutable("unzip"), installExecutable("zip"));
-    public static final String INSTALL_SYSSTAT = installPackage(ImmutableMap.of("onlyifmissing", "iostat"), "sysstat");
-
-    /**
-     * Returns commands to download the URL, saving as the given file. Will try each URL in turn until one is successful
-     * (see `curl -f` documentation).
-     */
-    public static List<String> commandsToDownloadUrlsAs(List<String> urls, String saveAs) {
-        return Arrays.asList(INSTALL_CURL, 
-                require(simpleDownloadUrlAs(urls, saveAs), "Could not retrieve "+saveAs+". Tried: " + Joiner.on(", ").join(urls), 9));
-    }
-
-    /**
-     * Returns commands to download the URL, saving as the given file. Will try each URL in turn until one is successful.
-     * It allows setting a minimum TLS version to avoid this error: <code> <curl: (35) Peer reports incompatible or unsupported protocol version./code>
-     * (see `curl -f` documentation).
-     */
-    public static List<String> commandsToDownloadUrlsAsWithMinimumTlsVersion(List<String> urls, String saveAs, String tlsVersion) {
-        return Arrays.asList(INSTALL_CURL,
-                require(simpleDownloadUrlAs(urls, saveAs, tlsVersion), "Could not retrieve "+saveAs+". Tried: " + Joiner.on(", ").join(urls), 9));
-    }
-
-    public static String commandToDownloadUrlsAs(List<String> urls, String saveAs) {
-        return chain(INSTALL_CURL, 
-                require(simpleDownloadUrlAs(urls, saveAs), "Could not retrieve "+saveAs+". Tried: " + Joiner.on(", ").join(urls), 9));
-    }
-    public static String commandToDownloadUrlAs(String url, String saveAs) {
-        return chain(INSTALL_CURL, 
-                require(simpleDownloadUrlAs(Arrays.asList(url), saveAs), "Could not retrieve "+saveAs+" from " + url, 9));
-    }
-
-    /**
-     * Returns command to download the URL, sending the output to stdout --
-     * suitable for redirect by appending " | tar xvf".
-     * Will try each URL in turn until one is successful
-     */
-    public static String downloadToStdout(List<String> urls) {
-        return chain(
-                INSTALL_CURL + " > /dev/null", 
-                require(simpleDownloadUrlAs(urls, null), 
-                        "Could not retrieve file. Tried: " + Joiner.on(", ").join(urls), 9));
-    }
-    
-    /** as {@link #downloadToStdout(List)} but varargs for convenience */
-    public static String downloadToStdout(String ...urls) {
-        return downloadToStdout(Arrays.asList(urls));
-    }
-
-    /**
-     * Same as {@link simpleDownloadUrlAs(List, String)}, except does not install curl, and does not exit on failure,
-     * and if saveAs is null it downloads it so stdout.
-     */
-    public static String simpleDownloadUrlAs(List<String> urls, String saveAs) {
-        return simpleDownloadUrlAs(urls, null, null, saveAs, null);
-    }
-
-    /**
-     * Same as {@link simpleDownloadUrlAs(List, String, String)}, except does not install curl, and does not exit on failure,
-     * and if saveAs is null it downloads it so stdout.
-     */
-    public static String simpleDownloadUrlAs(List<String> urls, String saveAs, String tlsVersion) {
-        return simpleDownloadUrlAs(urls, null, null, saveAs, tlsVersion);
-    }
-
-    public static String simpleDownloadUrlAs(List<String> urls, String user, String password, String saveAs, String tlsVersion) {
-        if (urls.isEmpty()) throw new IllegalArgumentException("No URLs supplied to download "+saveAs);
-        
-        List<String> commands = new ArrayList<String>();
-        for (String url : urls) {
-            String command = tlsVersion == null ?
-                    "curl -f -L -k --retry 10 --keepalive-time 30 --speed-time 30 " :
-                    "curl --tlsv" + tlsVersion + " -f -L -k --retry 10 --keepalive-time 30 --speed-time 30 " ;
-            if (user!=null && password!=null) {
-               command = command + format("-u %s:%s ", user, password);
-            }
-            command = command + format("\"%s\"", url);
-            if (saveAs!=null) {
-                command = command + format(" -o %s", saveAs);
-            }
-            commands.add(command);
-        }
-        return alternatives(commands);
-    }
-
-    private static Object getFlag(Map<?,?> flags, String flagName, Object defaultValue) {
-        Object found = flags.get(flagName);
-        return found == null ? defaultValue : found;
-    }
-
-    /**
-     * Install a particular Java runtime, fails if not possible.
-     * <p>
-     * <em><strong>Note</strong> Java 8 is not yet supported on SUSE</em>
-     *
-     * @return The command to install the given Java runtime.
-     * @see #installJava6OrFail()
-     * @see #installJava7Or6OrFail()
-     * @see #installJavaLatestOrFail()
-     */
-    public static String installJava(int version) {
-        Preconditions.checkArgument(version == 6 || version == 7 || version == 8, "Supported Java versions are 6, 7, or 8");
-        List<String> commands = new LinkedList<String>();
-        commands.add(ok(addOpenJDKPPK()));
-        commands.add(installPackageOr(MutableMap.of("apt", "openjdk-" + version + "-jdk","yum", "java-1." + version + ".0-openjdk-devel"), null,
-                ifExecutableElse1("zypper", chainGroup(
-                        ok(sudo("zypper --non-interactive addrepo http://download.opensuse.org/repositories/Java:/openjdk6:/Factory/SLE_11_SP3 java_sles_11")),
-                        ok(sudo("zypper --non-interactive addrepo http://download.opensuse.org/repositories/Java:/openjdk6:/Factory/openSUSE_11.4 java_suse_11")),
-                        ok(sudo("zypper --non-interactive addrepo http://download.opensuse.org/repositories/Java:/openjdk6:/Factory/openSUSE_12.3 java_suse_12")),
-                        ok(sudo("zypper --non-interactive addrepo http://download.opensuse.org/repositories/Java:/openjdk6:/Factory/openSUSE_13.1 java_suse_13")),
-                        alternatives(installPackageOrFail(MutableMap.of("zypper", "java-1_" + version + "_0-openjdk-devel"), null),
-                                installPackageOrFail(MutableMap.of("zypper", "java-1_" + version + "_0-ibm"), null))))));
-        commands.add(ok(upgradeNSS()));
-        return chainGroup(commands);
-    }
-
-    public static String installJava6() {
-        return installJava(6);
-    }
-    public static String installJava7() {
-        return installJava(7);
-    }
-    public static String installJava8() {
-        return installJava(8);
-    }
-
-    public static String installJava6IfPossible() {
-        return ok(installJava6());
-    }
-    public static String installJava7IfPossible() {
-        return ok(installJava7());
-    }
-    public static String installJava8IfPossible() {
-        return ok(installJava8());
-    }
-
-    public static String installJava6OrFail() {
-        return alternatives(installJava6(), fail("java 6 install failed", 9));
-    }
-    public static String installJava7OrFail() {
-        return alternatives(installJava7(), fail("java 7 install failed", 9));
-    }
-    public static String installJava7Or6OrFail() {
-        return alternatives(installJava7(), installJava6(), fail("java install failed", 9));
-    }
-    public static String installJavaLatestOrFail() {
-        return alternatives(installJava8(), installJava7(), installJava6(), fail("java latest install failed", 9));
-    }
-
-    public static String installJavaLatestOrWarn() {
-        return alternatives(installJava8(), installJava7(), installJava6(), warn("java latest install failed, entity may subsequently fail"));
-    }
-
-    /**
-     * Adds the PPA for OpenJDK for older JDK versions (7 and lower) required by some software (e.g. JBoss)
-     */
-    public static String addOpenJDKPPK(){
-        return chainGroup(
-                sudo("sudo add-apt-repository -y ppa:openjdk-r/ppa"),
-                sudo("sudo apt-get update"));
-    }
-
-    /**
-     * Returns a command which upgrades NSS on Yum based machines - Addresses https://issues.apache.org/jira/browse/BROOKLYN-320
-     * @return command
-     */
-    public static String upgradeNSS(){
-        return chainGroup(
-                "which yum",
-                sudo("yum -y upgrade nss"));
-    }
-
-    /** cats the given text to the given command, using bash << multi-line input syntax */
-    public static String pipeTextTo(String text, String command) {
-        return "cat << EOL_BROOKLYN | "+command+"\n"
-                +text
-                +"\n"+"EOL_BROOKLYN\n";
-    }
-
-    public static String pipeTextToFile(String text, String filepath) {
-        return "cat > \"" + filepath + "\" << EOF_BROOKLYN\n"
-                + text + "\n"
-                + "EOF_BROOKLYN\n";
-    }
-
-    public static String prependToEtcHosts(String ip, String... hostnames) {
-        String tempFileId = "bak"+Identifiers.makeRandomId(4);
-        return sudo(String.format("sed -i."+tempFileId+" -e '1i\\\n%s %s' /etc/hosts", ip, Joiner.on(" ").join(hostnames)));
-    }
-    
-    public static String appendToEtcHosts(String ip, String... hostnames) {
-        // Using sed rather than `echo ... >> /etc/hosts` because when embedded inside sudo, 
-        // the redirect doesn't get executed by sudo.
-        String tempFileId = "bak"+Identifiers.makeRandomId(4);
-        return sudo(String.format("sed -i."+tempFileId+" -e '$a\\\n%s %s' /etc/hosts", ip, Joiner.on(" ").join(hostnames)));
-    }
-
-    /**
-     * Sets the hostname, splitting the given hostname if it contains a dot to include the unqualified and fully qualified names.
-     *  
-     * @see {@link #setHostname(String, String)}
-     */
-    @Beta
-    public static List<String> setHostname(String newHostname) {
-        // See http://www.dns-sd.org/trailingdotsindomainnames.html.
-        // If we are given "abcd." then let's pass that as-is to setHostname("abcd.", null)
-        
-        if (newHostname.indexOf(".") > 0) {
-            String hostPart = newHostname.substring(0, newHostname.indexOf("."));
-            String domainPart = newHostname.substring(hostPart.length()+1);
-            return setHostname(hostPart, domainPart);
-        } else {
-            return setHostname(newHostname, null);
-        }
-    }
-    
-    /**
-     * Sets the hostname to {@code hostPart + "." + domainPart}, or if domainPart is null/empty then {code hostPart}.
-     * 
-     * @param hostPart
-     * @param domainPart
-     * @return
-     */
-    @Beta
-    public static List<String> setHostname(String hostPart, String domainPart) {
-        // See:
-        // - http://www.rackspace.com/knowledge_center/article/centos-hostname-change
-        // - https://wiki.debian.org/HowTo/ChangeHostname
-        // - http://askubuntu.com/questions/9540/how-do-i-change-the-computer-name
-        //
-        // We prepend in /etc/hosts, to ensure the right fqn appears first.
-        //    e.g. comment in http://askubuntu.com/questions/158957/how-to-set-the-fully-qualified-domain-name-in-12-04
-        //    says "It's important to note that the first domain in /etc/hosts should be your FQDN. "
-        //
-        // TODO Should we run `sudo service hostname restart` or `sudo /etc/init.d/hostname restart`?
-        //      I don't think we need to because we've run `sudo hostname <newname>`
-        //
-        // TODO What if /etc/sysconfig/network doesn't have a line for HOSTNAME=...?
-        //
-        // TODO What about hostPart ending in "." - see http://www.dns-sd.org/trailingdotsindomainnames.html
-        //      for what that means in DNS. However, hostname is not the same as the DNS name (hostnames 
-        //      predate the invention of DNS! - although frequently the DNS name has the same first portion 
-        //      as the hostname) so the link you gave is not relevant. However despite searching Google and 
-        //      man pages I [Ricard] am unable to find a reference which clearly states what characters are 
-        //      relevant in a hostname. I think it's safest to assume that the hostname is just [a-z,0-9,-] 
-        //      and no dots at all.
-        
-        checkNotNull(hostPart, "hostPart");
-        checkArgument(!hostPart.contains("."), "hostPart '%s' must not contain '.'", hostPart);
-
-        String tempFileId = "bak"+Identifiers.makeRandomId(4);
-        
-        List<String> allhostnames = Lists.newArrayList();
-        String fqdn = hostPart;
-        if (Strings.isNonBlank(domainPart)) {
-            fqdn = hostPart+"."+domainPart;
-            allhostnames.add(fqdn);
-        }
-        allhostnames.add(hostPart);
-        allhostnames.add("localhost");
-        
-        return ImmutableList.of(
-                sudo("sed -i."+tempFileId+" -e 's/^127.0.0.1/# Replaced by Brooklyn\\\n#127.0.0.1/' /etc/hosts"),
-                prependToEtcHosts("127.0.0.1", allhostnames.toArray(new String[allhostnames.size()])),
-                ifFileExistsElse0("/etc/sysconfig/network", sudo("sed -i."+tempFileId+" -e 's/^HOSTNAME=.*$/HOSTNAME="+hostPart+"/' /etc/sysconfig/network")),
-                ifFileExistsElse0("/etc/hostname", sudo("sed -i."+tempFileId+" -e 's/^[a-zA-Z_0-9].*$/"+hostPart+"/' /etc/hostname")),
-                sudo("hostname "+hostPart));
-    }
+    private static final BashCommandsConfigurable instance = BashCommandsConfigurable.newInstance();
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static final String INSTALL_TAR = instance.INSTALL_TAR;
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static final String INSTALL_CURL = instance.INSTALL_CURL;
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static final String INSTALL_WGET = instance.INSTALL_WGET;
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static final String INSTALL_ZIP = instance.INSTALL_ZIP;
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static final String INSTALL_UNZIP = instance.INSTALL_UNZIP;
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static final String INSTALL_SYSSTAT = instance.INSTALL_SYSSTAT;
+
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String installExecutable(Map<?,?> flags, String executable) { return instance.installExecutable(flags, executable); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String installExecutable(String executable) { return instance.installExecutable(executable); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String quiet(String command) { return instance.quiet(command); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String ok(String command) { return instance.ok(command); }
+
+    public static String sudo(String command) { return instance.sudo(command); }
+
+    public static String authSudo(String command, String password) { return instance.authSudo(command, password); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String sudoAsUser(String user, String command) { return instance.sudoAsUser(user, command); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String addSbinPathCommand() { return instance.addSbinPathCommand(); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String sbinPath() { return instance.sbinPath(); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String executeCommandThenAsUserTeeOutputToFile(String commandWhoseOutputToWrite, String user, String file) { return instance.executeCommandThenAsUserTeeOutputToFile(commandWhoseOutputToWrite, user, file); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String dontRequireTtyForSudo() { return instance.dontRequireTtyForSudo(); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String generateKeyInDotSshIdRsaIfNotThere() { return instance.generateKeyInDotSshIdRsaIfNotThere(); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String ifFileExistsElse0(String path, String command) { return instance.ifFileExistsElse0(path, command); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String ifFileExistsElse1(String path, String command) { return instance.ifFileExistsElse1(path, command); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String ifExecutableElse0(String executable, String command) { return instance.ifExecutableElse0(executable, command); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String ifExecutableElse1(String executable, String command) { return instance.ifExecutableElse1(executable, command); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String ifNotExecutable(String command, String statement) { return instance.ifNotExecutable(command, statement); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String onlyIfExecutableMissing(String executable, String command) { return instance.onlyIfExecutableMissing(executable, command); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String ifExecutableElse(String command, String ifNotExist, String ifExist) { return instance.ifExecutableElse(command, ifNotExist, ifExist); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String ifExecutableDoesNotExistElse(String command, String ifNotExist, String ifExist) { return instance.ifExecutableDoesNotExistElse(command, ifNotExist, ifExist); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static ImmutableList<String> ifExecutableElse(String command, List<String> ifNotExist, List<String> ifExist) { return instance.ifExecutableElse(command, ifNotExist, ifExist); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static ImmutableList<String> ifExecutableDoesNotExistElse(String command, List<String> ifNotExist, List<String> ifExist) { return instance.ifExecutableDoesNotExistElse(command, ifNotExist, ifExist); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String chain(Collection<String> commands) { return instance.chain(commands); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String chain(String ...commands) { return instance.chain(commands); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String chainGroup(Collection<String> commands) { return instance.chainGroup(commands); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String chainGroup(String ...commands) { return instance.chainGroup(commands); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String chainSubshell(Collection<String> commands) { return instance.chainSubshell(commands); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String chainSubshell(String ...commands) { return instance.chainSubshell(commands); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String alternatives(Collection<String> commands) { return instance.alternatives(commands); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String alternatives(String ...commands) { return instance.alternatives(commands); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String alternativesGroup(Collection<String> commands) { return instance.alternativesGroup(commands); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String alternativesGroup(String ...commands) { return instance.alternativesGroup(commands); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String alternativesSubshell(Collection<String> commands) { return instance.alternativesSubshell(commands); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String alternativesSubshell(String ...commands) { return instance.alternativesSubshell(commands); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String formatIfNotNull(String pattern, Object arg) { return instance.formatIfNotNull(pattern, arg); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String installPackage(String packageDefaultName) { return instance.installPackage(packageDefaultName); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String installPackage(Map<?,?> flags, String packageDefaultName) { return instance.installPackage(flags, packageDefaultName); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String installPackageOrFail(Map<?,?> flags, String packageDefaultName) { return instance.installPackageOrFail(flags, packageDefaultName); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String installPackageOr(Map<?,?> flags, String packageDefaultName, String optionalCommandToRunIfNone) { return instance.installPackageOr(flags, packageDefaultName, optionalCommandToRunIfNone); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String warn(String message) { return instance.warn(message); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String fail(String message, int code) { return instance.fail(message, code); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String require(String command, String failureMessage, int exitCode) { return instance.require(command, failureMessage, exitCode); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String require(String command, String failureMessage) { return instance.require(command, failureMessage); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String requireTest(String test, String failureMessage, int exitCode) { return instance.requireTest(test, failureMessage, exitCode); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String requireTest(String test, String failureMessage) { return instance.requireTest(test, failureMessage); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String requireFile(String file) { return instance.requireFile(file); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String requireExecutable(String command) { return instance.requireExecutable(command); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String waitForFileContents(String file, String desiredContent, Duration timeout, boolean failOnTimeout) { return instance.waitForFileContents(file, desiredContent, timeout, failOnTimeout); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String waitForFileExists(String file, Duration timeout, boolean failOnTimeout) { return instance.waitForFileExists(file, timeout, failOnTimeout); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String waitForPortFree(int port, Duration timeout, boolean failOnTimeout) { return instance.waitForPortFree(port, timeout, failOnTimeout); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String unzip(String file, String targetDir) { return instance.unzip(file, targetDir); }
+
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static List<String> commandsToDownloadUrlsAs(List<String> urls, String saveAs) { return instance.commandsToDownloadUrlsAs(urls, saveAs); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static List<String> commandsToDownloadUrlsAsWithMinimumTlsVersion(List<String> urls, String saveAs, String tlsVersion) { return instance.commandsToDownloadUrlsAsWithMinimumTlsVersion(urls, saveAs, tlsVersion); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String commandToDownloadUrlsAs(List<String> urls, String saveAs) { return instance.commandToDownloadUrlsAs(urls, saveAs); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String commandToDownloadUrlAs(String url, String saveAs) { return instance.commandToDownloadUrlAs(url, saveAs); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String downloadToStdout(List<String> urls) { return instance.downloadToStdout(urls); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String downloadToStdout(String ...urls) { return instance.downloadToStdout(urls); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String simpleDownloadUrlAs(List<String> urls, String saveAs) { return instance.simpleDownloadUrlAs(urls, saveAs); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String simpleDownloadUrlAs(List<String> urls, String saveAs, String tlsVersion) { return instance.simpleDownloadUrlAs(urls, saveAs, tlsVersion); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String simpleDownloadUrlAs(List<String> urls, String user, String password, String saveAs, String tlsVersion) { return instance.simpleDownloadUrlAs(urls, user, password, saveAs, tlsVersion); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String installJava(int version) { return instance.installJava(version); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String installJava6() { return instance.installJava6(); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String installJava7() { return instance.installJava7(); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String installJava8() { return instance.installJava8(); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String installJava6IfPossible() { return instance.installJava6IfPossible(); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String installJava7IfPossible() { return instance.installJava7IfPossible(); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String installJava8IfPossible() { return instance.installJava8IfPossible(); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String installJava6OrFail() { return instance.installJava6OrFail(); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String installJava7OrFail() { return instance.installJava7OrFail(); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String installJava7Or6OrFail() { return instance.installJava7Or6OrFail(); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String installJavaLatestOrFail() { return instance.installJavaLatestOrFail(); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String installJavaLatestOrWarn() { return instance.installJavaLatestOrWarn(); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String addOpenJDKPPK() { return instance.addOpenJDKPPK(); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String upgradeNSS() { return instance.upgradeNSS(); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String pipeTextTo(String text, String command) { return instance.pipeTextTo(text, command); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String pipeTextToFile(String text, String filepath) { return instance.pipeTextToFile(text, filepath); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String prependToEtcHosts(String ip, String... hostnames) { return instance.prependToEtcHosts(ip, hostnames); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static String appendToEtcHosts(String ip, String... hostnames) { return instance.appendToEtcHosts(ip, hostnames); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static List<String> setHostname(String newHostname) { return instance.setHostname(newHostname); }
+
+    @Deprecated /** @deprecated since 1.1 use {@link BashCommandsConfigurable} */
+    public static List<String> setHostname(String hostPart, String domainPart) { return instance.setHostname(hostPart, domainPart); }
+
 }
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/ssh/BashCommands.java b/utils/common/src/main/java/org/apache/brooklyn/util/ssh/BashCommandsConfigurable.java
similarity index 82%
copy from utils/common/src/main/java/org/apache/brooklyn/util/ssh/BashCommands.java
copy to utils/common/src/main/java/org/apache/brooklyn/util/ssh/BashCommandsConfigurable.java
index d60c9a9a35..111282f16b 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/util/ssh/BashCommands.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/ssh/BashCommandsConfigurable.java
@@ -18,31 +18,38 @@
  */
 package org.apache.brooklyn.util.ssh;
 
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static java.lang.String.format;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.brooklyn.util.collections.MutableMap;
-import org.apache.brooklyn.util.text.Identifiers;
-import org.apache.brooklyn.util.text.Strings;
-import org.apache.brooklyn.util.text.StringEscapes.BashStringEscapes;
-import org.apache.brooklyn.util.time.Duration;
-
 import com.google.common.annotations.Beta;
 import com.google.common.base.Joiner;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.text.Identifiers;
+import org.apache.brooklyn.util.text.StringEscapes.BashStringEscapes;
+import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.time.Duration;
+
+import java.util.*;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.lang.String.format;
 
-public class BashCommands {
+public class BashCommandsConfigurable {
+
+    public static BashCommandsConfigurable newInstance() {
+        return new BashCommandsConfigurable();
+    }
+
+    private boolean ignoreCerts = true;
+
+    public BashCommandsConfigurable withIgnoreCerts(Boolean ignoreCerts) {
+        if (ignoreCerts!=null) this.ignoreCerts = ignoreCerts;
+        return this;
+    }
+
+    public boolean isIgnoreCerts() { return this.ignoreCerts; }
 
     /**
      * Returns a string for checking whether the given executable is available,
@@ -50,25 +57,25 @@ public class BashCommands {
      * <p/>
      * Uses {@link #installPackage} and accepts the same flags e.g. for apt, yum, rpm.
      */
-    public static String installExecutable(Map<?,?> flags, String executable) {
+    public String installExecutable(Map<?,?> flags, String executable) {
         return onlyIfExecutableMissing(executable, installPackage(flags, executable));
     }
 
-    public static String installExecutable(String executable) {
+    public String installExecutable(String executable) {
         return installExecutable(MutableMap.of(), executable);
     }
 
     /**
      * Returns a command with all output redirected to /dev/null
      */
-    public static String quiet(String command) {
+    public String quiet(String command) {
         return format("(%s > /dev/null 2>&1)", command);
     }
 
     /**
      * Returns a command that always exits successfully
      */
-    public static String ok(String command) {
+    public String ok(String command) {
         return String.format("(%s || true)", command);
     }
 
@@ -90,7 +97,7 @@ public class BashCommands {
      * <p/>
      * If null is supplied, it is returned (sometimes used to indicate no command desired).
      */
-    public static String sudo(String command) {
+    public String sudo(String command) {
         if (command==null) return null;
         if (command.startsWith("( ") || command.endsWith(" &"))
             return sudoNew(command);
@@ -98,7 +105,7 @@ public class BashCommands {
             return sudoOld(command);
     }
 
-    public static String authSudo(String command, String password) {
+    public String authSudo(String command, String password) {
         checkNotNull(password, "password must not be null");
         if (command==null) return null;
         if (command.startsWith("( ") || command.endsWith(" &")) {
@@ -132,7 +139,7 @@ public class BashCommands {
      * @deprecated since 0.7.0 semantics of this are fiddly, e.g. whether user gets their environment */
     @Deprecated
     @Beta
-    public static String sudoAsUser(String user, String command) {
+    public String sudoAsUser(String user, String command) {
         return sudoAsUserOld(user, command);
     }
     
@@ -149,17 +156,17 @@ public class BashCommands {
 //        return "{ sudo -n -S -i -u "+user+" -- "+BashStringEscapes.wrapBash(command)+" ; }";
 //    }
 
-    public static String addSbinPathCommand() {
+    public String addSbinPathCommand() {
         return "export PATH=" + sbinPath();
     }
 
-    public static String sbinPath() {
+    public String sbinPath() {
         return "$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
     }
 
     /** executes a command, then as user tees the output to the given file. 
      * useful e.g. for appending to a file which is only writable by root or a priveleged user. */
-    public static String executeCommandThenAsUserTeeOutputToFile(String commandWhoseOutputToWrite, String user, String file) {
+    public String executeCommandThenAsUserTeeOutputToFile(String commandWhoseOutputToWrite, String user, String file) {
         return format("{ %s | sudo -E -n -u %s -s -- tee -a %s ; }",
                 commandWhoseOutputToWrite, user, file);
     }
@@ -172,12 +179,12 @@ public class BashCommands {
      * will try to detect the changes and do nothing.
      * <p>
      * This command must be run with allocatePTY set as a flag to ssh.
-     * See {@link SshTasks#dontRequireTtyForSudo(SshMachineLocation, OnFailingTask)} which sets that up. 
+     * See SshTasks dontRequireTtyForSudo(SshMachineLocation, OnFailingTask) which sets that up.
      * <p>
      * Having a TTY for sudo seems like another case of imaginary security which is just irritating.
      * Like water restrictions at airport security.
      */
-    public static String dontRequireTtyForSudo() {
+    public String dontRequireTtyForSudo() {
         String sudoersFileName =  "/etc/sudoers";
         String tmpSuffix = Identifiers.makeRandomLowercaseId(6); // Avoid clobbering
 
@@ -193,7 +200,7 @@ public class BashCommands {
     }
 
     /** generates ~/.ssh/id_rsa if that file does not exist */
-    public static String generateKeyInDotSshIdRsaIfNotThere() {
+    public String generateKeyInDotSshIdRsaIfNotThere() {
         return "[ -f ~/.ssh/id_rsa ] || ( mkdir -p ~/.ssh ; chmod 700 ~/.ssh ; ssh-keygen -t rsa -N '' -f ~/.ssh/id_rsa )";
     }
     
@@ -206,14 +213,14 @@ public class BashCommands {
      * Executed as  { { not-file-exists && ok ; } || command ; }  for portability.
      * ("if [ ... ] ; then xxx ; else xxx ; fi" syntax is not quite as portable, I seem to recall (not sure, Alex Aug 2013).) 
      */
-    public static String ifFileExistsElse0(String path, String command) {
+    public String ifFileExistsElse0(String path, String command) {
         return alternativesGroup(
                 chainGroup(format("test ! -e %s", path), "true"),
                 command);
     }
     /** as {@link #ifFileExistsElse0(String, String)} but returns non-zero if the test fails (also returns non-zero if the command fails,
      * so you can't tell the difference :( -- we need if ; then ; else ; fi semantics for that I think, but not sure how portable that is) */
-    public static String ifFileExistsElse1(String path, String command) {
+    public String ifFileExistsElse1(String path, String command) {
         return chainGroup(format("test -e %s", path), command);
     }
 
@@ -222,14 +229,14 @@ public class BashCommands {
      * if the command runs and fails that exit is preserved (but if the executable is not on the path exit code is zero).
      * @see #ifFileExistsElse0(String, String) for implementation discussion, using <code>{ { test -z `which executable` && true ; } || command ; } 
      */
-    public static String ifExecutableElse0(String executable, String command) {
+    public String ifExecutableElse0(String executable, String command) {
         return alternativesGroup(
                 chainGroup(format("test -z `which %s`", executable), "true"),
                 command);
     }
 
     /** as {@link #ifExecutableElse0(String, String)} but returns 1 if the test fails (also returns non-zero if the command fails) */
-    public static String ifExecutableElse1(String executable, String command) {
+    public String ifExecutableElse1(String executable, String command) {
         return chainGroup(format("which %s", executable), command);
     }
 
@@ -241,7 +248,7 @@ public class BashCommands {
      * @param statement
      * @return command
      */
-    public static String ifNotExecutable(String command, String statement) {
+    public String ifNotExecutable(String command, String statement) {
         return String.format("{ { test ! -z `which %s`; } || { %s; } }", command, statement);
     }
 
@@ -250,7 +257,7 @@ public class BashCommands {
      * if the command runs and fails that exit is preserved (but if the executable is not on the path exit code is zero).
      * @see #ifFileExistsElse0(String, String) for implementation discussion, using <code>{ { test -z `which executable` && true ; } || command ; }
      */
-    public static String onlyIfExecutableMissing(String executable, String command) {
+    public String onlyIfExecutableMissing(String executable, String command) {
         return alternativesGroup(format("which %s", executable), command);
     }
 
@@ -258,11 +265,11 @@ public class BashCommands {
      * @deprecated As of release 0.10.0, replaced by {@link #ifExecutableDoesNotExistElse(String, String, String)}
      */
     @Deprecated
-    public static String ifExecutableElse(String command, String ifNotExist, String ifExist) {
+    public String ifExecutableElse(String command, String ifNotExist, String ifExist) {
         return ifExecutableDoesNotExistElse(command, ifNotExist, ifExist);
     }
 
-    public static String ifExecutableDoesNotExistElse(String command, String ifNotExist, String ifExist) {
+    public String ifExecutableDoesNotExistElse(String command, String ifNotExist, String ifExist) {
         return com.google.common.base.Joiner.on('\n').join(
                 ifExecutableDoesNotExistElse(command, ImmutableList.<String>of(ifNotExist), ImmutableList.<String>of(ifExist)));
     }
@@ -271,11 +278,11 @@ public class BashCommands {
      * @deprecated  As of release 0.10.0, replaced by {@link #ifExecutableDoesNotExistElse(String, List, List)}
      */
     @Deprecated
-    public static ImmutableList<String> ifExecutableElse(String command, List<String> ifNotExist, List<String> ifExist) {
+    public ImmutableList<String> ifExecutableElse(String command, List<String> ifNotExist, List<String> ifExist) {
         return ifExecutableDoesNotExistElse(command, ifNotExist, ifExist);
     }
 
-    public static ImmutableList<String> ifExecutableDoesNotExistElse(String command, List<String> ifNotExist, List<String> ifExist) {
+    public ImmutableList<String> ifExecutableDoesNotExistElse(String command, List<String> ifNotExist, List<String> ifExist) {
         return ImmutableList.<String>builder()
                 .add(String.format("if test -z `which %s`; then", command))
                 .addAll(ifNotExist)
@@ -290,37 +297,37 @@ public class BashCommands {
      * This currently runs as a subshell (so exits are swallowed) but behaviour may be changed imminently. 
      * (Use {@link #chainGroup(Collection)} or {@link #chainSubshell(Collection)} to be clear.)
      */
-    public static String chain(Collection<String> commands) {
+    public String chain(Collection<String> commands) {
         return "( " + Strings.join(commands, " && ") + " )";
     }
 
     /** Convenience for {@link #chain(Collection)} */
-    public static String chain(String ...commands) {
+    public String chain(String ...commands) {
         return "( " + Strings.join(commands, " && ") + " )";
     }
 
     /** As {@link #chain(Collection)}, but explicitly using { } grouping characters
      * to ensure exits are propagated. */
-    public static String chainGroup(Collection<String> commands) {
+    public String chainGroup(Collection<String> commands) {
         // spaces required around curly braces
         return "{ " + Strings.join(commands, " && ") + " ; }";
     }
 
     /** As {@link #chainGroup(Collection)} */
-    public static String chainGroup(String ...commands) {
+    public String chainGroup(String ...commands) {
         return "{ " + Strings.join(commands, " && ") + " ; }";
     }
 
     /** As {@link #chain(Collection)}, but explicitly using ( ) grouping characters
      * to ensure exits are caught. */
-    public static String chainSubshell(Collection<String> commands) {
+    public String chainSubshell(Collection<String> commands) {
         // the spaces are not required, but it might be possible that a (( expr )) is interpreted differently
         // (won't hurt to have the spaces in any case!) 
         return "( " + Strings.join(commands, " && ") + " )";
     }
 
     /** As {@link #chainSubshell(Collection)} */
-    public static String chainSubshell(String ...commands) {
+    public String chainSubshell(String ...commands) {
         return "( " + Strings.join(commands, " && ") + "  )";
     }
 
@@ -329,47 +336,47 @@ public class BashCommands {
      * This currently runs as a subshell (so exits are swallowed) but behaviour may be changed imminently. 
      * (Use {@link #alternativesGroup(Collection)} or {@link #alternativesSubshell(Collection)} to be clear.)
      */
-    public static String alternatives(Collection<String> commands) {
+    public String alternatives(Collection<String> commands) {
         return "( " + Strings.join(commands, " || ") + " )";
     }
 
     /** As {@link #alternatives(Collection)} */
-    public static String alternatives(String ...commands) {
+    public String alternatives(String ...commands) {
         return "( " + Strings.join(commands, " || ") + " )";
     }
 
     /** As {@link #alternatives(Collection)}, but explicitly using { } grouping characters
      * to ensure exits are propagated. */
-    public static String alternativesGroup(Collection<String> commands) {
+    public String alternativesGroup(Collection<String> commands) {
         // spaces required around curly braces
         return "{ " + Strings.join(commands, " || ") + " ; }";
     }
 
     /** As {@link #alternativesGroup(Collection)} */
-    public static String alternativesGroup(String ...commands) {
+    public String alternativesGroup(String ...commands) {
         return "{ " + Strings.join(commands, " || ") + " ; }";
     }
 
     /** As {@link #alternatives(Collection)}, but explicitly using ( ) grouping characters
      * to ensure exits are caught. */
-    public static String alternativesSubshell(Collection<String> commands) {
+    public String alternativesSubshell(Collection<String> commands) {
         // the spaces are not required, but it might be possible that a (( expr )) is interpreted differently
         // (won't hurt to have the spaces in any case!) 
         return "( " + Strings.join(commands, " || ") + " )";
     }
 
     /** As {@link #alternativesSubshell(Collection)} */
-    public static String alternativesSubshell(String ...commands) {
+    public String alternativesSubshell(String ...commands) {
         return "( " + Strings.join(commands, " || ") + "  )";
     }
 
     /** returns the pattern formatted with the given arg if the arg is not null, otherwise returns null */
-    public static String formatIfNotNull(String pattern, Object arg) {
+    public String formatIfNotNull(String pattern, Object arg) {
         if (arg==null) return null;
         return format(pattern, arg);
     }
     
-    public static String installPackage(String packageDefaultName) {
+    public String installPackage(String packageDefaultName) {
         return installPackage(MutableMap.of(), packageDefaultName);
     }
     /**
@@ -387,17 +394,17 @@ public class BashCommands {
      * installPackage(ImmutableMap.of("onlyifmissing", "curl"), "curl");
      * </pre>
      */
-    public static String installPackage(Map<?,?> flags, String packageDefaultName) {
+    public String installPackage(Map<?,?> flags, String packageDefaultName) {
         return installPackageOr(flags, packageDefaultName, null);
     }
-    public static String installPackageOrFail(Map<?,?> flags, String packageDefaultName) {
+    public String installPackageOrFail(Map<?,?> flags, String packageDefaultName) {
         return installPackageOr(flags, packageDefaultName, "exit 9");
     }
-    public static String installPackageOr(Map<?,?> flags, String packageDefaultName, String optionalCommandToRunIfNone) {
+    public String installPackageOr(Map<?,?> flags, String packageDefaultName, String optionalCommandToRunIfNone) {
         String ifMissing = (String) flags.get("onlyifmissing");
-        String zypperInstall = formatIfNotNull("zypper --non-interactive --no-gpg-checks install %s", getFlag(flags, "zypper", packageDefaultName));
+        String zypperInstall = formatIfNotNull("zypper --non-interactive"+(isIgnoreCerts() ? " --no-gpg-checks" : "")+" install %s", getFlag(flags, "zypper", packageDefaultName));
         String aptInstall = formatIfNotNull("apt-get install -y --allow-unauthenticated %s", getFlag(flags, "apt", packageDefaultName));
-        String yumInstall = formatIfNotNull("yum -y --nogpgcheck install %s", getFlag(flags, "yum", packageDefaultName));
+        String yumInstall = formatIfNotNull("yum -y"+(isIgnoreCerts() ? " --nogpgcheck" : "")+" install %s", getFlag(flags, "yum", packageDefaultName));
         String brewInstall = formatIfNotNull("brew install %s", getFlag(flags, "brew", packageDefaultName));
         String portInstall = formatIfNotNull("port install %s", getFlag(flags, "port", packageDefaultName));
 
@@ -442,48 +449,48 @@ public class BashCommands {
         return alternatives(commands);
     }
     
-    public static String warn(String message) {
+    public String warn(String message) {
         return "( echo "+BashStringEscapes.wrapBash(message)+" | tee /dev/stderr )";
     }
 
     /** returns a command which logs a message to stdout and stderr then exits with the given error code */
-    public static String fail(String message, int code) {
+    public String fail(String message, int code) {
         return chainGroup(warn(message), "exit "+code);
     }
 
     /** requires the command to have a non-zero exit code; e.g.
      * <code>require("which foo", "Command foo must be found", 1)</code> */
-    public static String require(String command, String failureMessage, int exitCode) {
+    public String require(String command, String failureMessage, int exitCode) {
         return alternativesGroup(command, fail(failureMessage, exitCode));
     }
 
     /** as {@link #require(String, String, int)} but returning the original exit code */
-    public static String require(String command, String failureMessage) {
+    public String require(String command, String failureMessage) {
         return alternativesGroup(command, chainGroup("EXIT_CODE=$?", warn(failureMessage), "exit $EXIT_CODE"));
     }
 
     /** requires the test to pass, as valid bash `test` arguments; e.g.
      * <code>requireTest("-f /etc/hosts", "Hosts file must exist", 1)</code> */
-    public static String requireTest(String test, String failureMessage, int exitCode) {
+    public String requireTest(String test, String failureMessage, int exitCode) {
         return require("test "+test, failureMessage, exitCode);
     }
 
     /** as {@link #requireTest(String, String, int)} but returning the original exit code */
-    public static String requireTest(String test, String failureMessage) {
+    public String requireTest(String test, String failureMessage) {
         return require("test "+test, failureMessage);
     }
 
     /** fails with nice error if the given file does not exist */
-    public static String requireFile(String file) {
+    public String requireFile(String file) {
         return requireTest("-f "+BashStringEscapes.wrapBash(file), "The required file \""+file+"\" does not exist");
     }
 
     /** fails with nice error if the given file does not exist */
-    public static String requireExecutable(String command) {
+    public String requireExecutable(String command) {
         return require("which "+BashStringEscapes.wrapBash(command), "The required executable \""+command+"\" does not exist");
     }
 
-    public static String waitForFileContents(String file, String desiredContent, Duration timeout, boolean failOnTimeout) {
+    public String waitForFileContents(String file, String desiredContent, Duration timeout, boolean failOnTimeout) {
         long secs = Math.max(timeout.toSeconds(), 1);
         
         List<String> commands = ImmutableList.of(
@@ -502,7 +509,7 @@ public class BashCommands {
         return Joiner.on("\n").join(commands);
     }
 
-    public static String waitForFileExists(String file, Duration timeout, boolean failOnTimeout) {
+    public String waitForFileExists(String file, Duration timeout, boolean failOnTimeout) {
         long secs = Math.max(timeout.toSeconds(), 1);
         
         List<String> commands = ImmutableList.of(
@@ -519,7 +526,7 @@ public class BashCommands {
         return Joiner.on("\n").join(commands);
     }
 
-    public static String waitForPortFree(int port, Duration timeout, boolean failOnTimeout) {
+    public String waitForPortFree(int port, Duration timeout, boolean failOnTimeout) {
         long secs = Math.max(timeout.toSeconds(), 1);
 
         // TODO How platform-dependent are the args + output format of netstat?
@@ -533,7 +540,7 @@ public class BashCommands {
         
         List<String> commands = ImmutableList.of(
                 "for i in {1.."+secs+"}; do",
-                "    "+BashCommands.requireExecutable("netstat"),
+                "    "+ requireExecutable("netstat"),
                 "    "+alternativesGroup(
                         chainGroup("which awk", "AWK_EXEC=awk"), 
                         chainGroup("which gawk", "AWK_EXEC=gawk"), 
@@ -551,22 +558,22 @@ public class BashCommands {
         return Joiner.on("\n").join(commands);
     }
 
-    public static String unzip(String file, String targetDir) {
+    public String unzip(String file, String targetDir) {
         return "unzip " + file + (Strings.isNonBlank(targetDir) ? " -d "+targetDir : "");
     }
 
-    public static final String INSTALL_TAR = installExecutable("tar");
-    public static final String INSTALL_CURL = installExecutable("curl");
-    public static final String INSTALL_WGET = installExecutable("wget");
-    public static final String INSTALL_ZIP = installExecutable("zip");
-    public static final String INSTALL_UNZIP = alternatives(installExecutable("unzip"), installExecutable("zip"));
-    public static final String INSTALL_SYSSTAT = installPackage(ImmutableMap.of("onlyifmissing", "iostat"), "sysstat");
+    public final String INSTALL_TAR = installExecutable("tar");
+    public final String INSTALL_CURL = installExecutable("curl");
+    public final String INSTALL_WGET = installExecutable("wget");
+    public final String INSTALL_ZIP = installExecutable("zip");
+    public final String INSTALL_UNZIP = alternatives(installExecutable("unzip"), installExecutable("zip"));
+    public final String INSTALL_SYSSTAT = installPackage(ImmutableMap.of("onlyifmissing", "iostat"), "sysstat");
 
     /**
      * Returns commands to download the URL, saving as the given file. Will try each URL in turn until one is successful
      * (see `curl -f` documentation).
      */
-    public static List<String> commandsToDownloadUrlsAs(List<String> urls, String saveAs) {
+    public List<String> commandsToDownloadUrlsAs(List<String> urls, String saveAs) {
         return Arrays.asList(INSTALL_CURL, 
                 require(simpleDownloadUrlAs(urls, saveAs), "Could not retrieve "+saveAs+". Tried: " + Joiner.on(", ").join(urls), 9));
     }
@@ -576,16 +583,16 @@ public class BashCommands {
      * It allows setting a minimum TLS version to avoid this error: <code> <curl: (35) Peer reports incompatible or unsupported protocol version./code>
      * (see `curl -f` documentation).
      */
-    public static List<String> commandsToDownloadUrlsAsWithMinimumTlsVersion(List<String> urls, String saveAs, String tlsVersion) {
+    public List<String> commandsToDownloadUrlsAsWithMinimumTlsVersion(List<String> urls, String saveAs, String tlsVersion) {
         return Arrays.asList(INSTALL_CURL,
                 require(simpleDownloadUrlAs(urls, saveAs, tlsVersion), "Could not retrieve "+saveAs+". Tried: " + Joiner.on(", ").join(urls), 9));
     }
 
-    public static String commandToDownloadUrlsAs(List<String> urls, String saveAs) {
+    public String commandToDownloadUrlsAs(List<String> urls, String saveAs) {
         return chain(INSTALL_CURL, 
                 require(simpleDownloadUrlAs(urls, saveAs), "Could not retrieve "+saveAs+". Tried: " + Joiner.on(", ").join(urls), 9));
     }
-    public static String commandToDownloadUrlAs(String url, String saveAs) {
+    public String commandToDownloadUrlAs(String url, String saveAs) {
         return chain(INSTALL_CURL, 
                 require(simpleDownloadUrlAs(Arrays.asList(url), saveAs), "Could not retrieve "+saveAs+" from " + url, 9));
     }
@@ -595,7 +602,7 @@ public class BashCommands {
      * suitable for redirect by appending " | tar xvf".
      * Will try each URL in turn until one is successful
      */
-    public static String downloadToStdout(List<String> urls) {
+    public String downloadToStdout(List<String> urls) {
         return chain(
                 INSTALL_CURL + " > /dev/null", 
                 require(simpleDownloadUrlAs(urls, null), 
@@ -603,34 +610,34 @@ public class BashCommands {
     }
     
     /** as {@link #downloadToStdout(List)} but varargs for convenience */
-    public static String downloadToStdout(String ...urls) {
+    public String downloadToStdout(String ...urls) {
         return downloadToStdout(Arrays.asList(urls));
     }
 
     /**
-     * Same as {@link simpleDownloadUrlAs(List, String)}, except does not install curl, and does not exit on failure,
+     * Same as {@link #simpleDownloadUrlAs(List, String)}, except does not install curl, and does not exit on failure,
      * and if saveAs is null it downloads it so stdout.
      */
-    public static String simpleDownloadUrlAs(List<String> urls, String saveAs) {
+    public String simpleDownloadUrlAs(List<String> urls, String saveAs) {
         return simpleDownloadUrlAs(urls, null, null, saveAs, null);
     }
 
     /**
-     * Same as {@link simpleDownloadUrlAs(List, String, String)}, except does not install curl, and does not exit on failure,
+     * Same as {@link #simpleDownloadUrlAs(List, String, String)}, except does not install curl, and does not exit on failure,
      * and if saveAs is null it downloads it so stdout.
      */
-    public static String simpleDownloadUrlAs(List<String> urls, String saveAs, String tlsVersion) {
+    public String simpleDownloadUrlAs(List<String> urls, String saveAs, String tlsVersion) {
         return simpleDownloadUrlAs(urls, null, null, saveAs, tlsVersion);
     }
 
-    public static String simpleDownloadUrlAs(List<String> urls, String user, String password, String saveAs, String tlsVersion) {
+    public String simpleDownloadUrlAs(List<String> urls, String user, String password, String saveAs, String tlsVersion) {
         if (urls.isEmpty()) throw new IllegalArgumentException("No URLs supplied to download "+saveAs);
         
         List<String> commands = new ArrayList<String>();
         for (String url : urls) {
             String command = tlsVersion == null ?
-                    "curl -f -L -k --retry 10 --keepalive-time 30 --speed-time 30 " :
-                    "curl --tlsv" + tlsVersion + " -f -L -k --retry 10 --keepalive-time 30 --speed-time 30 " ;
+                    "curl -f -L"+(isIgnoreCerts() ? " -k" : "")+" --retry 10 --keepalive-time 30 --speed-time 30 " :
+                    "curl --tlsv" + tlsVersion + " -f -L "+(isIgnoreCerts() ? " -k" : "")+" --retry 10 --keepalive-time 30 --speed-time 30 " ;
             if (user!=null && password!=null) {
                command = command + format("-u %s:%s ", user, password);
             }
@@ -643,7 +650,7 @@ public class BashCommands {
         return alternatives(commands);
     }
 
-    private static Object getFlag(Map<?,?> flags, String flagName, Object defaultValue) {
+    private Object getFlag(Map<?,?> flags, String flagName, Object defaultValue) {
         Object found = flags.get(flagName);
         return found == null ? defaultValue : found;
     }
@@ -658,7 +665,7 @@ public class BashCommands {
      * @see #installJava7Or6OrFail()
      * @see #installJavaLatestOrFail()
      */
-    public static String installJava(int version) {
+    public String installJava(int version) {
         Preconditions.checkArgument(version == 6 || version == 7 || version == 8, "Supported Java versions are 6, 7, or 8");
         List<String> commands = new LinkedList<String>();
         commands.add(ok(addOpenJDKPPK()));
@@ -674,47 +681,47 @@ public class BashCommands {
         return chainGroup(commands);
     }
 
-    public static String installJava6() {
+    public String installJava6() {
         return installJava(6);
     }
-    public static String installJava7() {
+    public String installJava7() {
         return installJava(7);
     }
-    public static String installJava8() {
+    public String installJava8() {
         return installJava(8);
     }
 
-    public static String installJava6IfPossible() {
+    public String installJava6IfPossible() {
         return ok(installJava6());
     }
-    public static String installJava7IfPossible() {
+    public String installJava7IfPossible() {
         return ok(installJava7());
     }
-    public static String installJava8IfPossible() {
+    public String installJava8IfPossible() {
         return ok(installJava8());
     }
 
-    public static String installJava6OrFail() {
+    public String installJava6OrFail() {
         return alternatives(installJava6(), fail("java 6 install failed", 9));
     }
-    public static String installJava7OrFail() {
+    public String installJava7OrFail() {
         return alternatives(installJava7(), fail("java 7 install failed", 9));
     }
-    public static String installJava7Or6OrFail() {
+    public String installJava7Or6OrFail() {
         return alternatives(installJava7(), installJava6(), fail("java install failed", 9));
     }
-    public static String installJavaLatestOrFail() {
+    public String installJavaLatestOrFail() {
         return alternatives(installJava8(), installJava7(), installJava6(), fail("java latest install failed", 9));
     }
 
-    public static String installJavaLatestOrWarn() {
+    public String installJavaLatestOrWarn() {
         return alternatives(installJava8(), installJava7(), installJava6(), warn("java latest install failed, entity may subsequently fail"));
     }
 
     /**
      * Adds the PPA for OpenJDK for older JDK versions (7 and lower) required by some software (e.g. JBoss)
      */
-    public static String addOpenJDKPPK(){
+    public String addOpenJDKPPK(){
         return chainGroup(
                 sudo("sudo add-apt-repository -y ppa:openjdk-r/ppa"),
                 sudo("sudo apt-get update"));
@@ -724,31 +731,31 @@ public class BashCommands {
      * Returns a command which upgrades NSS on Yum based machines - Addresses https://issues.apache.org/jira/browse/BROOKLYN-320
      * @return command
      */
-    public static String upgradeNSS(){
+    public String upgradeNSS(){
         return chainGroup(
                 "which yum",
                 sudo("yum -y upgrade nss"));
     }
 
     /** cats the given text to the given command, using bash << multi-line input syntax */
-    public static String pipeTextTo(String text, String command) {
+    public String pipeTextTo(String text, String command) {
         return "cat << EOL_BROOKLYN | "+command+"\n"
                 +text
                 +"\n"+"EOL_BROOKLYN\n";
     }
 
-    public static String pipeTextToFile(String text, String filepath) {
+    public String pipeTextToFile(String text, String filepath) {
         return "cat > \"" + filepath + "\" << EOF_BROOKLYN\n"
                 + text + "\n"
                 + "EOF_BROOKLYN\n";
     }
 
-    public static String prependToEtcHosts(String ip, String... hostnames) {
+    public String prependToEtcHosts(String ip, String... hostnames) {
         String tempFileId = "bak"+Identifiers.makeRandomId(4);
         return sudo(String.format("sed -i."+tempFileId+" -e '1i\\\n%s %s' /etc/hosts", ip, Joiner.on(" ").join(hostnames)));
     }
     
-    public static String appendToEtcHosts(String ip, String... hostnames) {
+    public String appendToEtcHosts(String ip, String... hostnames) {
         // Using sed rather than `echo ... >> /etc/hosts` because when embedded inside sudo, 
         // the redirect doesn't get executed by sudo.
         String tempFileId = "bak"+Identifiers.makeRandomId(4);
@@ -761,7 +768,7 @@ public class BashCommands {
      * @see {@link #setHostname(String, String)}
      */
     @Beta
-    public static List<String> setHostname(String newHostname) {
+    public List<String> setHostname(String newHostname) {
         // See http://www.dns-sd.org/trailingdotsindomainnames.html.
         // If we are given "abcd." then let's pass that as-is to setHostname("abcd.", null)
         
@@ -782,7 +789,7 @@ public class BashCommands {
      * @return
      */
     @Beta
-    public static List<String> setHostname(String hostPart, String domainPart) {
+    public List<String> setHostname(String hostPart, String domainPart) {
         // See:
         // - http://www.rackspace.com/knowledge_center/article/centos-hostname-change
         // - https://wiki.debian.org/HowTo/ChangeHostname
@@ -826,4 +833,5 @@ public class BashCommands {
                 ifFileExistsElse0("/etc/hostname", sudo("sed -i."+tempFileId+" -e 's/^[a-zA-Z_0-9].*$/"+hostPart+"/' /etc/hostname")),
                 sudo("hostname "+hostPart));
     }
+
 }
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/ssh/IptablesCommands.java b/utils/common/src/main/java/org/apache/brooklyn/util/ssh/IptablesCommands.java
index d50454445a..6f5b42a114 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/util/ssh/IptablesCommands.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/ssh/IptablesCommands.java
@@ -18,14 +18,10 @@
  */
 package org.apache.brooklyn.util.ssh;
 
-import static org.apache.brooklyn.util.ssh.BashCommands.alternatives;
-import static org.apache.brooklyn.util.ssh.BashCommands.chain;
-import static org.apache.brooklyn.util.ssh.BashCommands.installPackage;
-import static org.apache.brooklyn.util.ssh.BashCommands.sudo;
-
 import com.google.common.annotations.Beta;
 import com.google.common.base.Optional;
 
+@Deprecated /** @deprecated since 1.1 use {@link IptablesCommandsConfigurable} */
 public class IptablesCommands {
 
     public enum Chain {
@@ -36,91 +32,56 @@ public class IptablesCommands {
         ACCEPT, REJECT, DROP, LOG
     }
 
-    /**
-     * @deprecated since 0.7; use {@link org.apache.brooklyn.util.net.Protocol}; kept for persisted state backwards compatibility.
-     */
-    @Deprecated
-    public enum Protocol {
-        TCP("tcp"), UDP("udp"), ALL("all");
-
-        final String protocol;
-
-        private Protocol(String protocol) {
-            this.protocol = protocol;
-        }
-
-        @Override
-        public String toString() {
-            return protocol;
-        }
-
-        org.apache.brooklyn.util.net.Protocol convert() {
-            switch (this) {
-                case TCP: return org.apache.brooklyn.util.net.Protocol.TCP;
-                case UDP: return org.apache.brooklyn.util.net.Protocol.UDP;
-                case ALL: return org.apache.brooklyn.util.net.Protocol.ALL;
-                default: throw new IllegalStateException("Unexpected protocol "+this);
-            }
-        }
-    }
-
-    @Beta // implementation not portable across distros
-    public static String iptablesService(String cmd) {
-        return sudo(alternatives(
-                BashCommands.ifExecutableElse1("service", "service iptables " + cmd),
-                "/sbin/service iptables " + cmd));
-    }
+    private static final IptablesCommandsConfigurable instance = new IptablesCommandsConfigurable(BashCommandsConfigurable.newInstance());
 
     @Beta // implementation not portable across distros
-    public static String iptablesServiceStop() {
-        return iptablesService("stop");
+    public String iptablesServiceStop() {
+        return instance.iptablesServiceStop();
     }
 
     @Beta // implementation not portable across distros
-    public static String iptablesServiceStart() {
-        return iptablesService("start");
+    public String iptablesServiceStart() {
+        return instance.iptablesServiceStart();
     }
 
     @Beta // implementation not portable across distros
-    public static String iptablesServiceRestart() {
-        return iptablesService("restart");
+    public String iptablesServiceRestart() {
+        return instance.iptablesServiceRestart();
     }
 
     @Beta // implementation not portable across distros
-    public static String iptablesServiceStatus() {
-        return iptablesService("status");
+    public String iptablesServiceStatus() {
+        return instance.iptablesServiceStatus();
     }
 
     @Beta // implementation not portable across distros
-    public static String firewalldService(String cmd) {
-        return sudo(alternatives(
-                BashCommands.ifExecutableElse1("systemctl", "systemctl " + cmd + " firewalld"),
-                "/usr/bin/systemctl " + cmd + " firewalld"));
+    public String firewalldService(String cmd) {
+        return instance.firewalldService(cmd);
     }
 
     @Beta // implementation not portable across distros
-    public static String firewalldServiceStop() {
-        return firewalldService("stop");
+    public String firewalldServiceStop() {
+        return instance.firewalldServiceStop();
     }
 
     @Beta // implementation not portable across distros
-    public static String firewalldServiceStart() {
-        return firewalldService("start");
+    public String firewalldServiceStart() {
+        return instance.firewalldServiceStart();
     }
 
     @Beta // implementation not portable across distros
-    public static String firewalldServiceRestart() {
-        return firewalldService("restart");
+    public String firewalldServiceRestart() {
+        return instance.firewalldServiceRestart();
     }
 
     @Beta // implementation not portable across distros
-    public static String firewalldServiceStatus() {
-        return firewalldService("status");
+    public String firewalldServiceStatus() {
+        return instance.firewalldServiceStatus();
     }
 
     @Beta // implementation not portable across distros
-    public static String firewalldServiceIsActive() {
-        return firewalldService("is-active");
+    public String firewalldServiceIsActive() {
+        return instance.firewalldServiceIsActive();
     }
 
     /**
@@ -129,9 +90,8 @@ public class IptablesCommands {
      * @return Returns the command that saves iptables rules on file.
      *
      */
-    public static String saveIptablesRules() {
-        return alternatives(sudo("service iptables save"),
-                            chain(installPackage("iptables-persistent"), sudo("/etc/init.d/iptables-persistent save")));
+    public String saveIptablesRules() {
+        return instance.saveIptablesRules();
     }
 
     /**
@@ -139,8 +99,8 @@ public class IptablesCommands {
      *
      * @return Returns the command that cleans up iptables rules.
      */
-    public static String cleanUpIptablesRules() {
-       return sudo("/sbin/iptables -F");
+    public String cleanUpIptablesRules() {
+        return instance.cleanUpIptablesRules();
     }
 
     /**
@@ -148,8 +108,8 @@ public class IptablesCommands {
      *
      * @return Returns the command that list all the iptables rules.
      */
-    public static String listIptablesRule() {
-       return sudo("/sbin/iptables -L -v -n");
+    public String listIptablesRule() {
+        return instance.listIptablesRule();
     }
 
     /**
@@ -158,8 +118,8 @@ public class IptablesCommands {
      * @return Returns the command that inserts a rule on top of the iptables'
      *         rules.
      */
-    public static String insertIptablesRule(Chain chain, org.apache.brooklyn.util.net.Protocol protocol, int port, Policy policy) {
-        return addIptablesRule("-I", chain, Optional.<String> absent(), protocol, port, policy);
+    public String insertIptablesRule(IptablesCommandsConfigurable.Chain chain, org.apache.brooklyn.util.net.Protocol protocol, int port, IptablesCommandsConfigurable.Policy policy) {
+        return instance.insertIptablesRule(chain, protocol, port, policy);
     }
 
     /**
@@ -168,8 +128,8 @@ public class IptablesCommands {
      * @return Returns the command that inserts a rule on top of the iptables'
      *         rules.
      */
-    public static String insertIptablesRule(Chain chain, String networkInterface, org.apache.brooklyn.util.net.Protocol protocol, int port, Policy policy) {
-        return addIptablesRule("-I", chain, Optional.of(networkInterface), protocol, port, policy);
+    public String insertIptablesRule(IptablesCommandsConfigurable.Chain chain, String networkInterface, org.apache.brooklyn.util.net.Protocol protocol, int port, IptablesCommandsConfigurable.Policy policy) {
+        return instance.insertIptablesRule(chain, networkInterface, protocol, port, policy);
     }
 
     /**
@@ -177,8 +137,8 @@ public class IptablesCommands {
      *
      * @return Returns the command that appends a rule to iptables.
      */
-    public static String appendIptablesRule(Chain chain, org.apache.brooklyn.util.net.Protocol protocol, int port, Policy policy) {
-        return addIptablesRule("-A", chain, Optional.<String> absent(), protocol, port, policy);
+    public String appendIptablesRule(IptablesCommandsConfigurable.Chain chain, org.apache.brooklyn.util.net.Protocol protocol, int port, IptablesCommandsConfigurable.Policy policy) {
+        return instance.appendIptablesRule(chain, protocol, port, policy);
     }
 
     /**
@@ -186,8 +146,8 @@ public class IptablesCommands {
      *
      * @return Returns the command that appends a rule to iptables.
      */
-    public static String appendIptablesRule(Chain chain, String networkInterface, org.apache.brooklyn.util.net.Protocol protocol, int port, Policy policy) {
-        return addIptablesRule("-A", chain, Optional.of(networkInterface), protocol, port, policy);
+    public String appendIptablesRule(IptablesCommandsConfigurable.Chain chain, String networkInterface, org.apache.brooklyn.util.net.Protocol protocol, int port, IptablesCommandsConfigurable.Policy policy) {
+        return instance.appendIptablesRule(chain, networkInterface, protocol, port, policy);
     }
 
     /**
@@ -195,14 +155,8 @@ public class IptablesCommands {
      *
      * @return Returns the command that creates a rule for iptables.
      */
-    public static String addIptablesRule(String direction, Chain chain, Optional<String> networkInterface, org.apache.brooklyn.util.net.Protocol protocol, int port, Policy policy) {
-        String addIptablesRule;
-        if(networkInterface.isPresent()) {
-           addIptablesRule = String.format("/sbin/iptables %s %s -i %s -p %s --dport %d -j %s", direction, chain, networkInterface.get(), protocol, port, policy);
-        } else {
-           addIptablesRule = String.format("/sbin/iptables %s %s -p %s --dport %d -j %s", direction, chain, protocol, port, policy);
-        }
-        return sudo(addIptablesRule);
+    public String addIptablesRule(String direction, IptablesCommandsConfigurable.Chain chain, Optional<String> networkInterface, org.apache.brooklyn.util.net.Protocol protocol, int port, IptablesCommandsConfigurable.Policy policy) {
+        return instance.addIptablesRule(direction, chain, networkInterface, protocol, port, policy);
     }
 
     /**
@@ -210,24 +164,18 @@ public class IptablesCommands {
      *
      * @return Returns the command that adds firewalld direct rule.
      */
-    public static String addFirewalldRule(Chain chain, org.apache.brooklyn.util.net.Protocol protocol, int port, Policy policy) {
-        return addFirewalldRule(chain, Optional.<String>absent(), protocol, port, policy);
+    public String addFirewalldRule(IptablesCommandsConfigurable.Chain chain, org.apache.brooklyn.util.net.Protocol protocol, int port, IptablesCommandsConfigurable.Policy policy) {
+        return instance.addFirewalldRule(chain, protocol, port, policy);
     }
-    
+
     /**
      * Returns the command that adds firewalld direct rule.
      *
      * @return Returns the command that adds firewalld direct rule.
      */
-    public static String addFirewalldRule(Chain chain, Optional<String> networkInterface, org.apache.brooklyn.util.net.Protocol protocol, int port, Policy policy) {
-        String command = new String("/usr/bin/firewall-cmd");
-        String commandPermanent = new String("/usr/bin/firewall-cmd --permanent");
-        
-        String interfaceParameter = String.format("%s", networkInterface.isPresent() ? " -i " + networkInterface.get() : "");
-        
-        String commandParameters = String.format(" --direct --add-rule ipv4 filter %s 0 %s -p %s --dport %d -j %s", 
-                                                                chain, interfaceParameter,  protocol, port, policy);
-        
-        return sudo(chain(command + commandParameters, commandPermanent + commandParameters));
+    public String addFirewalldRule(IptablesCommandsConfigurable.Chain chain, Optional<String> networkInterface, org.apache.brooklyn.util.net.Protocol protocol, int port, IptablesCommandsConfigurable.Policy policy) {
+
+        return instance.addFirewalldRule(chain, networkInterface, protocol, port, policy);
     }
+
 }
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/ssh/IptablesCommands.java b/utils/common/src/main/java/org/apache/brooklyn/util/ssh/IptablesCommandsConfigurable.java
similarity index 69%
copy from utils/common/src/main/java/org/apache/brooklyn/util/ssh/IptablesCommands.java
copy to utils/common/src/main/java/org/apache/brooklyn/util/ssh/IptablesCommandsConfigurable.java
index d50454445a..4ddb1a96d6 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/util/ssh/IptablesCommands.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/ssh/IptablesCommandsConfigurable.java
@@ -18,15 +18,10 @@
  */
 package org.apache.brooklyn.util.ssh;
 
-import static org.apache.brooklyn.util.ssh.BashCommands.alternatives;
-import static org.apache.brooklyn.util.ssh.BashCommands.chain;
-import static org.apache.brooklyn.util.ssh.BashCommands.installPackage;
-import static org.apache.brooklyn.util.ssh.BashCommands.sudo;
-
 import com.google.common.annotations.Beta;
 import com.google.common.base.Optional;
 
-public class IptablesCommands {
+public class IptablesCommandsConfigurable {
 
     public enum Chain {
         INPUT, FORWARD, OUTPUT
@@ -36,6 +31,12 @@ public class IptablesCommands {
         ACCEPT, REJECT, DROP, LOG
     }
 
+    private final BashCommandsConfigurable bash;
+
+    public IptablesCommandsConfigurable(BashCommandsConfigurable bash) {
+        this.bash = bash;
+    }
+
     /**
      * @deprecated since 0.7; use {@link org.apache.brooklyn.util.net.Protocol}; kept for persisted state backwards compatibility.
      */
@@ -65,61 +66,61 @@ public class IptablesCommands {
     }
 
     @Beta // implementation not portable across distros
-    public static String iptablesService(String cmd) {
-        return sudo(alternatives(
-                BashCommands.ifExecutableElse1("service", "service iptables " + cmd),
+    public String iptablesService(String cmd) {
+        return bash.sudo(bash.alternatives(
+                bash.ifExecutableElse1("service", "service iptables " + cmd),
                 "/sbin/service iptables " + cmd));
     }
 
     @Beta // implementation not portable across distros
-    public static String iptablesServiceStop() {
+    public String iptablesServiceStop() {
         return iptablesService("stop");
     }
 
     @Beta // implementation not portable across distros
-    public static String iptablesServiceStart() {
+    public String iptablesServiceStart() {
         return iptablesService("start");
     }
 
     @Beta // implementation not portable across distros
-    public static String iptablesServiceRestart() {
+    public String iptablesServiceRestart() {
         return iptablesService("restart");
     }
 
     @Beta // implementation not portable across distros
-    public static String iptablesServiceStatus() {
+    public String iptablesServiceStatus() {
         return iptablesService("status");
     }
 
     @Beta // implementation not portable across distros
-    public static String firewalldService(String cmd) {
-        return sudo(alternatives(
-                BashCommands.ifExecutableElse1("systemctl", "systemctl " + cmd + " firewalld"),
+    public String firewalldService(String cmd) {
+        return bash.sudo(bash.alternatives(
+                bash.ifExecutableElse1("systemctl", "systemctl " + cmd + " firewalld"),
                 "/usr/bin/systemctl " + cmd + " firewalld"));
     }
 
     @Beta // implementation not portable across distros
-    public static String firewalldServiceStop() {
+    public String firewalldServiceStop() {
         return firewalldService("stop");
     }
 
     @Beta // implementation not portable across distros
-    public static String firewalldServiceStart() {
+    public String firewalldServiceStart() {
         return firewalldService("start");
     }
 
     @Beta // implementation not portable across distros
-    public static String firewalldServiceRestart() {
+    public String firewalldServiceRestart() {
         return firewalldService("restart");
     }
 
     @Beta // implementation not portable across distros
-    public static String firewalldServiceStatus() {
+    public String firewalldServiceStatus() {
         return firewalldService("status");
     }
 
     @Beta // implementation not portable across distros
-    public static String firewalldServiceIsActive() {
+    public String firewalldServiceIsActive() {
         return firewalldService("is-active");
     }
 
@@ -129,9 +130,9 @@ public class IptablesCommands {
      * @return Returns the command that saves iptables rules on file.
      *
      */
-    public static String saveIptablesRules() {
-        return alternatives(sudo("service iptables save"),
-                            chain(installPackage("iptables-persistent"), sudo("/etc/init.d/iptables-persistent save")));
+    public String saveIptablesRules() {
+        return bash.alternatives(bash.sudo("service iptables save"),
+                bash.chain(bash.installPackage("iptables-persistent"), bash.sudo("/etc/init.d/iptables-persistent save")));
     }
 
     /**
@@ -139,8 +140,8 @@ public class IptablesCommands {
      *
      * @return Returns the command that cleans up iptables rules.
      */
-    public static String cleanUpIptablesRules() {
-       return sudo("/sbin/iptables -F");
+    public String cleanUpIptablesRules() {
+       return bash.sudo("/sbin/iptables -F");
     }
 
     /**
@@ -148,8 +149,8 @@ public class IptablesCommands {
      *
      * @return Returns the command that list all the iptables rules.
      */
-    public static String listIptablesRule() {
-       return sudo("/sbin/iptables -L -v -n");
+    public String listIptablesRule() {
+       return bash.sudo("/sbin/iptables -L -v -n");
     }
 
     /**
@@ -158,7 +159,7 @@ public class IptablesCommands {
      * @return Returns the command that inserts a rule on top of the iptables'
      *         rules.
      */
-    public static String insertIptablesRule(Chain chain, org.apache.brooklyn.util.net.Protocol protocol, int port, Policy policy) {
+    public String insertIptablesRule(Chain chain, org.apache.brooklyn.util.net.Protocol protocol, int port, Policy policy) {
         return addIptablesRule("-I", chain, Optional.<String> absent(), protocol, port, policy);
     }
 
@@ -168,7 +169,7 @@ public class IptablesCommands {
      * @return Returns the command that inserts a rule on top of the iptables'
      *         rules.
      */
-    public static String insertIptablesRule(Chain chain, String networkInterface, org.apache.brooklyn.util.net.Protocol protocol, int port, Policy policy) {
+    public String insertIptablesRule(Chain chain, String networkInterface, org.apache.brooklyn.util.net.Protocol protocol, int port, Policy policy) {
         return addIptablesRule("-I", chain, Optional.of(networkInterface), protocol, port, policy);
     }
 
@@ -177,7 +178,7 @@ public class IptablesCommands {
      *
      * @return Returns the command that appends a rule to iptables.
      */
-    public static String appendIptablesRule(Chain chain, org.apache.brooklyn.util.net.Protocol protocol, int port, Policy policy) {
+    public String appendIptablesRule(Chain chain, org.apache.brooklyn.util.net.Protocol protocol, int port, Policy policy) {
         return addIptablesRule("-A", chain, Optional.<String> absent(), protocol, port, policy);
     }
 
@@ -186,7 +187,7 @@ public class IptablesCommands {
      *
      * @return Returns the command that appends a rule to iptables.
      */
-    public static String appendIptablesRule(Chain chain, String networkInterface, org.apache.brooklyn.util.net.Protocol protocol, int port, Policy policy) {
+    public String appendIptablesRule(Chain chain, String networkInterface, org.apache.brooklyn.util.net.Protocol protocol, int port, Policy policy) {
         return addIptablesRule("-A", chain, Optional.of(networkInterface), protocol, port, policy);
     }
 
@@ -195,14 +196,14 @@ public class IptablesCommands {
      *
      * @return Returns the command that creates a rule for iptables.
      */
-    public static String addIptablesRule(String direction, Chain chain, Optional<String> networkInterface, org.apache.brooklyn.util.net.Protocol protocol, int port, Policy policy) {
+    public String addIptablesRule(String direction, Chain chain, Optional<String> networkInterface, org.apache.brooklyn.util.net.Protocol protocol, int port, Policy policy) {
         String addIptablesRule;
         if(networkInterface.isPresent()) {
            addIptablesRule = String.format("/sbin/iptables %s %s -i %s -p %s --dport %d -j %s", direction, chain, networkInterface.get(), protocol, port, policy);
         } else {
            addIptablesRule = String.format("/sbin/iptables %s %s -p %s --dport %d -j %s", direction, chain, protocol, port, policy);
         }
-        return sudo(addIptablesRule);
+        return bash.sudo(addIptablesRule);
     }
 
     /**
@@ -210,7 +211,7 @@ public class IptablesCommands {
      *
      * @return Returns the command that adds firewalld direct rule.
      */
-    public static String addFirewalldRule(Chain chain, org.apache.brooklyn.util.net.Protocol protocol, int port, Policy policy) {
+    public String addFirewalldRule(Chain chain, org.apache.brooklyn.util.net.Protocol protocol, int port, Policy policy) {
         return addFirewalldRule(chain, Optional.<String>absent(), protocol, port, policy);
     }
     
@@ -219,7 +220,7 @@ public class IptablesCommands {
      *
      * @return Returns the command that adds firewalld direct rule.
      */
-    public static String addFirewalldRule(Chain chain, Optional<String> networkInterface, org.apache.brooklyn.util.net.Protocol protocol, int port, Policy policy) {
+    public String addFirewalldRule(Chain chain, Optional<String> networkInterface, org.apache.brooklyn.util.net.Protocol protocol, int port, Policy policy) {
         String command = new String("/usr/bin/firewall-cmd");
         String commandPermanent = new String("/usr/bin/firewall-cmd --permanent");
         
@@ -228,6 +229,6 @@ public class IptablesCommands {
         String commandParameters = String.format(" --direct --add-rule ipv4 filter %s 0 %s -p %s --dport %d -j %s", 
                                                                 chain, interfaceParameter,  protocol, port, policy);
         
-        return sudo(chain(command + commandParameters, commandPermanent + commandParameters));
+        return bash.sudo(bash.chain(command + commandParameters, commandPermanent + commandParameters));
     }
 }


[brooklyn-server] 03/06: update other files to use new BashCommandsConfigurable rather than statics

Posted by he...@apache.org.
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 e2084ef0da333d6d7ff39e45cc77869b237e01e7
Author: Alex Heneveld <al...@cloudsoft.io>
AuthorDate: Fri Jul 29 02:08:56 2022 +0100

    update other files to use new BashCommandsConfigurable rather than statics
---
 .../core/effector/ssh/SshEffectorTasks.java        |  34 ++++---
 .../LocalhostMachineProvisioningLocation.java      |   2 +-
 .../brooklyn/util/core/file/ArchiveUtils.java      |  15 ++-
 .../util/core/internal/ssh/ShellAbstractTool.java  |   1 +
 .../brooklyn/util/core/task/ssh/SshTasks.java      |  21 ++--
 .../ssh/internal/AbstractSshExecTaskFactory.java   |   4 +-
 .../util/core/task/system/ProcessTaskFactory.java  |   2 +
 .../util/core/task/system/ProcessTaskStub.java     |  11 ++-
 .../util/core/task/system/ProcessTaskWrapper.java  |   2 +-
 .../internal/AbstractProcessTaskFactory.java       |  14 ++-
 .../system/internal/SystemProcessTaskFactory.java  |   4 +-
 .../persist/deserializingClassRenames.properties   |   4 +-
 .../core/effector/ssh/SshEffectorTasksTest.java    |   5 +-
 .../util/core/ssh/BashCommandsIntegrationTest.java | 109 ++++++++++-----------
 .../brooklyn/location/jclouds/JcloudsLocation.java |  43 ++++----
 .../policy/jclouds/os/CreateUserPolicy.java        |  11 ++-
 .../location/jclouds/JcloudsSuseLiveTest.java      |   4 +-
 ...eJcloudsLocationUserLoginAndConfigLiveTest.java |   5 +-
 .../brooklyn/entity/brooklynnode/BrooklynNode.java |   4 +-
 .../entity/brooklynnode/BrooklynNodeSshDriver.java |   7 +-
 .../entity/java/JavaSoftwareProcessSshDriver.java  |   7 +-
 .../brooklyn/entity/machine/MachineInitTasks.java  |  31 +++---
 .../entity/machine/SetHostnameCustomizer.java      |  12 ++-
 .../entity/machine/SetLimitsCustomizer.java        |   4 +-
 .../base/AbstractSoftwareProcessSshDriver.java     |  26 ++---
 .../base/VanillaSoftwareProcessSshDriver.java      |   8 +-
 .../system_service/InitdServiceInstaller.java      |  14 +--
 .../entity/AbstractMultiDistroLiveTest.java        |   5 +-
 .../test/mysql/DynamicToyMySqlEntityBuilder.java   |  21 ++--
 .../system_service/SystemServiceEnricherTest.java  |   4 +-
 .../util/ssh/IptablesCommandsFirewalldTest.java    |  21 ++--
 .../brooklyn/util/ssh/IptablesCommandsTest.java    |  20 ++--
 32 files changed, 269 insertions(+), 206 deletions(-)

diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasks.java b/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasks.java
index bf6f0b783b..1ea2b816b6 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasks.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasks.java
@@ -56,7 +56,7 @@ import org.apache.brooklyn.util.core.task.ssh.internal.PlainSshExecTaskFactory;
 import org.apache.brooklyn.util.core.task.system.ProcessTaskFactory;
 import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
 import org.apache.brooklyn.util.guava.Maybe;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -253,20 +253,24 @@ public class SshEffectorTasks {
     }
 
 
+    @Deprecated /** @deprecated  since 1.1 supply bash context */
+    public static SshEffectorTaskFactory<Integer> codePidFromFileRunning(final String pidFile) {
+        return codePidFromFileRunning(BashCommandsConfigurable.newInstance(), pidFile);
+    }
     /** task which returns 0 if pid in the given file is running;
      * method accepts wildcards so long as they match a single file on the remote end
      * <p>
      * returns 1 if no matching file, 
      * 1 if matching file but no matching process,
      * and 2 if 2+ matching files */
-    public static SshEffectorTaskFactory<Integer> codePidFromFileRunning(final String pidFile) {
-        return ssh(BashCommands.chain(
+    public static SshEffectorTaskFactory<Integer> codePidFromFileRunning(BashCommandsConfigurable bash, final String pidFile) {
+        return ssh(bash.chain(
                 // this fails, but isn't an error
-                BashCommands.requireTest("-f "+pidFile, "The PID file "+pidFile+" does not exist."),
+                bash.requireTest("-f "+pidFile, "The PID file "+pidFile+" does not exist."),
                 // this fails and logs an error picked up later
-                BashCommands.requireTest("`ls "+pidFile+" | wc -w` -eq 1", "ERROR: there are multiple matching PID files"),
+                bash.requireTest("`ls "+pidFile+" | wc -w` -eq 1", "ERROR: there are multiple matching PID files"),
                 // this fails and logs an error picked up later
-                BashCommands.require("cat "+pidFile, "ERROR: the PID file "+pidFile+" cannot be read (permissions?)."),
+                bash.require("cat "+pidFile, "ERROR: the PID file "+pidFile+" cannot be read (permissions?)."),
                 // finally check the process
                 "ps -p `cat "+pidFile+"`")).summary("PID file "+pidFile+" is-running check (exit code)")
                 .allowingNonZeroExitCode()
@@ -279,18 +283,26 @@ public class SshEffectorTasks {
                     }
                 });
     }
-    
+
+    @Deprecated /** @deprecated  since 1.1 supply bash context */
+    public static SshEffectorTaskFactory<?> requirePidFromFileRunning(String pidFile) {
+        return requirePidFromFileRunning(BashCommandsConfigurable.newInstance(), pidFile);
+    }
     /** task which fails if the pid in the given file is not running (or if there is no such PID file);
      * method accepts wildcards so long as they match a single file on the remote end (fails if 0 or 2+ matching files) */
-    public static SshEffectorTaskFactory<?> requirePidFromFileRunning(String pidFile) {
-        return codePidFromFileRunning(pidFile)
+    public static SshEffectorTaskFactory<?> requirePidFromFileRunning(BashCommandsConfigurable bash, String pidFile) {
+        return codePidFromFileRunning(bash, pidFile)
                 .summary("PID file "+pidFile+" is-running check (required)")
                 .requiringExitCodeZero("Process with PID from file "+pidFile+" is required to be running");
     }
 
-    /** as {@link #codePidFromFileRunning(String)} but returning boolean */
+    @Deprecated /** @deprecated  since 1.1 supply bash context */
     public static SshEffectorTaskFactory<Boolean> isPidFromFileRunning(String pidFile) {
-        return codePidFromFileRunning(pidFile).summary("PID file "+pidFile+" is-running check (boolean)").
+        return isPidFromFileRunning(BashCommandsConfigurable.newInstance(), pidFile);
+    }
+    /** as {@link #codePidFromFileRunning(BashCommandsConfigurable, String)} but returning boolean */
+    public static SshEffectorTaskFactory<Boolean> isPidFromFileRunning(BashCommandsConfigurable bash, String pidFile) {
+        return codePidFromFileRunning(bash, pidFile).summary("PID file "+pidFile+" is-running check (boolean)").
                 returning(new Function<ProcessTaskWrapper<?>, Boolean>() {
                     @Override
                     public Boolean apply(@Nullable ProcessTaskWrapper<?> input) { return ((Integer)0).equals(input.getExitCode()); }
diff --git a/core/src/main/java/org/apache/brooklyn/location/localhost/LocalhostMachineProvisioningLocation.java b/core/src/main/java/org/apache/brooklyn/location/localhost/LocalhostMachineProvisioningLocation.java
index 8656f5dd9b..e0537baacc 100644
--- a/core/src/main/java/org/apache/brooklyn/location/localhost/LocalhostMachineProvisioningLocation.java
+++ b/core/src/main/java/org/apache/brooklyn/location/localhost/LocalhostMachineProvisioningLocation.java
@@ -51,10 +51,10 @@ import org.apache.brooklyn.util.core.flags.SetFromFlag;
 import org.apache.brooklyn.util.core.internal.ssh.process.ProcessTool;
 import org.apache.brooklyn.util.core.mutex.MutexSupport;
 import org.apache.brooklyn.util.core.mutex.WithMutexes;
-import org.apache.brooklyn.util.exceptions.UserFacingException;
 import org.apache.brooklyn.util.net.Networking;
 import org.apache.brooklyn.util.os.Os;
 import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.time.Duration;
 import org.apache.brooklyn.util.time.Time;
 import org.slf4j.Logger;
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/file/ArchiveUtils.java b/core/src/main/java/org/apache/brooklyn/util/core/file/ArchiveUtils.java
index ea103f2b5c..2f16f0baed 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/file/ArchiveUtils.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/file/ArchiveUtils.java
@@ -45,7 +45,8 @@ import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.javalang.StackTraceSimplifier;
 import org.apache.brooklyn.util.net.Urls;
 import org.apache.brooklyn.util.os.Os;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
+import org.apache.brooklyn.util.ssh.IptablesCommandsConfigurable;
 import org.apache.brooklyn.util.stream.Streams;
 import org.apache.brooklyn.util.text.Strings;
 import org.slf4j.Logger;
@@ -109,19 +110,23 @@ public class ArchiveUtils {
         }
     }
 
+    @Deprecated /** @deprecated since 1.1 use {@link IptablesCommandsConfigurable} */
+    public static List<String> installCommands(String fileName) {
+        return installCommands(BashCommandsConfigurable.newInstance(), fileName);
+    }
     /**
      * Returns the list of commands used to install support for an archive with the given name.
      */
-    public static List<String> installCommands(String fileName) {
+    public static List<String> installCommands(BashCommandsConfigurable bash, String fileName) {
         List<String> commands = new LinkedList<String>();
         switch (ArchiveType.of(fileName)) {
             case TAR:
             case TGZ:
             case TBZ:
-                commands.add(BashCommands.INSTALL_TAR);
+                commands.add(bash.INSTALL_TAR);
                 break;
             case ZIP:
-                commands.add(BashCommands.INSTALL_UNZIP);
+                commands.add(bash.INSTALL_UNZIP);
                 break;
             case JAR:
             case WAR:
@@ -281,7 +286,7 @@ public class ArchiveUtils {
             }
 
             // extract, now using task if available
-            MutableList<String> commands = MutableList.copyOf(installCommands(destFile))
+            MutableList<String> commands = MutableList.copyOf(installCommands(BrooklynOsCommands.bash(machine.getManagementContext()), destFile))
                     .appendAll(extractCommands(destFile, tmpDir, destDir, false, keepArchiveAfterUnpacking));
             if (DynamicTasks.getTaskQueuingContext()!=null) {
                 result = DynamicTasks.queue(SshTasks.newSshExecTaskFactory(machine, commands.toArray(new String[0])).summary("extracting archive").requiringExitCodeZero()).get();
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/internal/ssh/ShellAbstractTool.java b/core/src/main/java/org/apache/brooklyn/util/core/internal/ssh/ShellAbstractTool.java
index befef2e33d..eb1e9cbbb2 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/internal/ssh/ShellAbstractTool.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/internal/ssh/ShellAbstractTool.java
@@ -37,6 +37,7 @@ import org.apache.brooklyn.util.collections.MutableList;
 import org.apache.brooklyn.util.core.flags.TypeCoercions;
 import org.apache.brooklyn.util.os.Os;
 import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.text.Identifiers;
 import org.apache.brooklyn.util.text.Strings;
 import org.apache.brooklyn.util.text.StringEscapes.BashStringEscapes;
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/SshTasks.java b/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/SshTasks.java
index d732177051..16066e9967 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/SshTasks.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/SshTasks.java
@@ -42,8 +42,10 @@ import org.apache.brooklyn.core.location.AbstractLocation;
 import org.apache.brooklyn.core.location.internal.LocationInternal;
 import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.util.collections.MutableList;
 import org.apache.brooklyn.util.collections.MutableSet;
 import org.apache.brooklyn.util.core.ResourceUtils;
+import org.apache.brooklyn.util.core.file.BrooklynOsCommands;
 import org.apache.brooklyn.util.core.internal.ssh.SshTool;
 import org.apache.brooklyn.util.core.task.DynamicTasks;
 import org.apache.brooklyn.util.core.task.Tasks;
@@ -52,7 +54,7 @@ import org.apache.brooklyn.util.core.task.system.ProcessTaskFactory;
 import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
 import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.net.Urls;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.stream.Streams;
 import org.apache.brooklyn.util.text.Identifiers;
 import org.apache.brooklyn.util.text.Strings;
@@ -178,10 +180,17 @@ public class SshTasks {
         }
         
         final String id = Identifiers.makeRandomId(6);
-        return newSshExecTaskFactory(machine, 
-                BashCommands.dontRequireTtyForSudo(),
-                // strange quotes are to ensure we don't match against echoed stdin
-                BashCommands.sudo("echo \"sudo\"-is-working-"+id))
+        return newSshExecTaskFactory(machine, "(commands to modify sudo config to allow tty)")
+                .commandModifier(x -> {
+                    Entity entity = BrooklynTaskTags.getTargetOrContextEntity(Tasks.current());
+                    BashCommandsConfigurable bash;
+                    if (entity!=null) bash = BrooklynOsCommands.bash(entity);
+                    else bash = BrooklynOsCommands.bash(machine.getManagementContext());
+                    return MutableList.of(
+                        bash.dontRequireTtyForSudo(),
+                        // strange quotes are to ensure we don't match against echoed stdin
+                        bash.sudo("echo \"sudo\"-is-working-"+id));
+            })
             .summary("patch /etc/sudoers to disable requiretty")
             .configure(SshTool.PROP_ALLOCATE_PTY, true)
             .allowingNonZeroExitCode()
@@ -216,7 +225,7 @@ public class SshTasks {
         return new Function<ProcessTaskWrapper<?>, String>() {
           @Override
           public String apply(@Nullable ProcessTaskWrapper<?> input) {
-            if (logger!=null) logger.info(input+" COMMANDS:\n"+Strings.join(input.getCommands(),"\n"));
+            if (logger!=null) logger.info(input+" COMMANDS:\n"+Strings.join(input.getCommands(true),"\n"));
             if (logger!=null) logger.info(input+" STDOUT:\n"+input.getStdout());
             if (logger!=null) logger.info(input+" STDERR:\n"+input.getStderr());
             if (requireZero && input.getExitCode()!=0) 
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/internal/AbstractSshExecTaskFactory.java b/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/internal/AbstractSshExecTaskFactory.java
index e61bd7cd01..4e9e08e0c0 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/internal/AbstractSshExecTaskFactory.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/internal/AbstractSshExecTaskFactory.java
@@ -60,9 +60,9 @@ public abstract class AbstractSshExecTaskFactory<T extends AbstractProcessTaskFa
             protected void run(ConfigBag config) {
                 Preconditions.checkNotNull(getMachine(), "machine");
                 if (Boolean.FALSE.equals(this.runAsScript)) {
-                    this.exitCode = getMachine().execCommands(config.getAllConfig(), getSummary(), commands, shellEnvironment);
+                    this.exitCode = getMachine().execCommands(config.getAllConfig(), getSummary(), getCommands(true), shellEnvironment);
                 } else { // runScript = null or TRUE
-                    this.exitCode = getMachine().execScript(config.getAllConfig(), getSummary(), commands, shellEnvironment);
+                    this.exitCode = getMachine().execScript(config.getAllConfig(), getSummary(), getCommands(true), shellEnvironment);
                 }
             }
             @Override
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskFactory.java b/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskFactory.java
index b7e8e3aab9..a46436d188 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskFactory.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskFactory.java
@@ -24,6 +24,7 @@ import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.util.core.internal.ssh.SshTool;
 import org.apache.brooklyn.util.core.task.system.ProcessTaskStub.ScriptReturnType;
 
+import java.util.List;
 import java.util.Map;
 import java.util.function.Function;
 
@@ -46,6 +47,7 @@ public interface ProcessTaskFactory<T> extends SimpleProcessTaskFactory<ProcessT
     ProcessTaskFactory<T> runAsCommand();
     ProcessTaskFactory<T> runAsScript();
     ProcessTaskFactory<T> runAsRoot();
+    ProcessTaskFactory<T> commandModifier(Function<List<String>, List<String>> modifier);
     @Override ProcessTaskFactory<T> environmentVariable(String key, String val);
     @Override ProcessTaskFactory<T> environmentVariables(Map<String,String> vars);
     @Override ProcessTaskFactory<T> summary(String summary);
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskStub.java b/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskStub.java
index 471fe54965..522dec4d40 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskStub.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskStub.java
@@ -39,6 +39,7 @@ public class ProcessTaskStub {
     
     // config data
     protected String summary;
+    protected Function<List<String>,List<String>> commandModifier;
     protected final ConfigBag config = ConfigBag.newInstance();
     
     public static enum ScriptReturnType { CUSTOM, EXIT_CODE, STDOUT_STRING, STDOUT_BYTES, STDERR_STRING, STDERR_BYTES }
@@ -53,12 +54,13 @@ public class ProcessTaskStub {
     protected final List<Function<ProcessTaskWrapper<?>, Void>> completionListeners = new ArrayList<Function<ProcessTaskWrapper<?>,Void>>();
 
     public ProcessTaskStub() {}
-    
+
     protected ProcessTaskStub(ProcessTaskStub source) {
-        commands.addAll(source.getCommands());
+        commands.addAll(source.getCommands(false));
         machine = source.getMachine();
         summary = source.getSummary();
         config.copy(source.getConfig());
+        commandModifier = source.commandModifier;
         returnResultTransformation = source.returnResultTransformation;
         returnType = source.returnType;
         runAsScript = source.runAsScript;
@@ -89,7 +91,10 @@ public class ProcessTaskStub {
     }
 
     public List<String> getCommands() {
-        return ImmutableList.copyOf(commands);
+        return getCommands(false);
+    }
+    public List<String> getCommands(boolean modified) {
+        return ImmutableList.copyOf(modified && commandModifier!=null ? commandModifier.apply(commands) : commands);
     }
  
     public List<Function<ProcessTaskWrapper<?>, Void>> getCompletionListeners() {
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskWrapper.java b/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskWrapper.java
index 4185ca2923..e19bc40f0e 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskWrapper.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskWrapper.java
@@ -145,7 +145,7 @@ public abstract class ProcessTaskWrapper<RET> extends ProcessTaskStub implements
             log.warn(message+" (throwing)");
             logProblemDetails("STDERR", stderrForReading(), 1024);
             logProblemDetails("STDOUT", stdoutForReading(), 1024);
-            logProblemDetails("STDIN", Streams.byteArrayOfString(Strings.join(commands,"\n")), 4096);
+            logProblemDetails("STDIN", Streams.byteArrayOfString(Strings.join(getCommands(true),"\n")), 4096);
             if (optionalCause!=null) throw new IllegalStateException(message, optionalCause);
             throw new IllegalStateException(message);
         }
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/task/system/internal/AbstractProcessTaskFactory.java b/core/src/main/java/org/apache/brooklyn/util/core/task/system/internal/AbstractProcessTaskFactory.java
index f1a22e6e8e..b76a228eaf 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/task/system/internal/AbstractProcessTaskFactory.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/task/system/internal/AbstractProcessTaskFactory.java
@@ -19,8 +19,10 @@
 package org.apache.brooklyn.util.core.task.system.internal;
 
 import java.util.Arrays;
+import java.util.List;
 import java.util.Map;
 import java.util.function.Function;
+import java.util.function.Supplier;
 
 import org.apache.brooklyn.api.location.MachineLocation;
 import org.slf4j.Logger;
@@ -61,6 +63,10 @@ public abstract class AbstractProcessTaskFactory<T extends AbstractProcessTaskFa
         return self();
     }
 
+    public List<String> getCommands() {
+        return this.commands;
+    }
+
     @Override
     public T add(Iterable<String> commandsToAdd) {
         Iterables.addAll(this.commands, commandsToAdd);
@@ -183,7 +189,7 @@ public abstract class AbstractProcessTaskFactory<T extends AbstractProcessTaskFa
         TaskBuilder<Object> tb = TaskBuilder.builder().dynamic(false).displayName(displayName);
         
         tb.tag(BrooklynTaskTags.tagForStream(BrooklynTaskTags.STREAM_STDIN, 
-                Streams.byteArrayOfString(Strings.join(commands, "\n"))));
+                Streams.byteArrayOfString(Strings.join(getCommands(true), "\n"))));
         tb.tag(BrooklynTaskTags.tagForEnvStream(BrooklynTaskTags.STREAM_ENV, shellEnvironment));
 
         return tb;
@@ -196,6 +202,12 @@ public abstract class AbstractProcessTaskFactory<T extends AbstractProcessTaskFa
         return self();
     }
 
+    /** allows caller to supply a command modifier, used to modify the commands set up. */
+    public T commandModifier(Function<List<String>, List<String>> commandModifier) {
+        this.commandModifier = commandModifier;
+        return self();
+    }
+
     @Override
     public <V> T configure(ConfigKey<V> key, V value) {
         config.configure(key, value);
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/task/system/internal/SystemProcessTaskFactory.java b/core/src/main/java/org/apache/brooklyn/util/core/task/system/internal/SystemProcessTaskFactory.java
index 3ecbbe6458..b15c9df3c0 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/task/system/internal/SystemProcessTaskFactory.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/task/system/internal/SystemProcessTaskFactory.java
@@ -93,9 +93,9 @@ public class SystemProcessTaskFactory<T extends SystemProcessTaskFactory<T,RET>,
         @Override
         protected void run(ConfigBag config) {
             if (Boolean.FALSE.equals(this.runAsScript)) {
-                this.exitCode = newExecWithLoggingHelpers().execCommands(config.getAllConfig(), getSummary(), getCommands(), getShellEnvironment());
+                this.exitCode = newExecWithLoggingHelpers().execCommands(config.getAllConfig(), getSummary(), getCommands(true), getShellEnvironment());
             } else { // runScript = null or TRUE
-                this.exitCode = newExecWithLoggingHelpers().execScript(config.getAllConfig(), getSummary(), getCommands(), getShellEnvironment());
+                this.exitCode = newExecWithLoggingHelpers().execScript(config.getAllConfig(), getSummary(), getCommands(true), getShellEnvironment());
             }
         }
         @Override
diff --git a/core/src/main/resources/org/apache/brooklyn/core/mgmt/persist/deserializingClassRenames.properties b/core/src/main/resources/org/apache/brooklyn/core/mgmt/persist/deserializingClassRenames.properties
index 857941732e..115a8e5806 100644
--- a/core/src/main/resources/org/apache/brooklyn/core/mgmt/persist/deserializingClassRenames.properties
+++ b/core/src/main/resources/org/apache/brooklyn/core/mgmt/persist/deserializingClassRenames.properties
@@ -1345,8 +1345,8 @@ brooklyn.util.os.Os
 brooklyn.util.pool.BasicPool                                                     : org.apache.brooklyn.util.pool.BasicPool
 brooklyn.util.pool.Lease                                                         : org.apache.brooklyn.util.pool.Lease
 brooklyn.util.pool.Pool                                                          : org.apache.brooklyn.util.pool.Pool
-brooklyn.util.ssh.BashCommands                                                   : org.apache.brooklyn.util.ssh.BashCommands
-brooklyn.util.ssh.IptablesCommands                                               : org.apache.brooklyn.util.ssh.IptablesCommands
+brooklyn.util.ssh.BashCommands                                                   : org.apache.brooklyn.util.ssh.BashCommandsConfigurable
+brooklyn.util.ssh.IptablesCommands                                               : org.apache.brooklyn.util.ssh.IptablesCommandsConfigurable
 brooklyn.util.stream.DelegatingPrintStream                                       : org.apache.brooklyn.util.stream.DelegatingPrintStream
 brooklyn.util.stream.IllegalOutputStream                                         : org.apache.brooklyn.util.stream.IllegalOutputStream
 brooklyn.util.stream.KnownSizeInputStream                                        : org.apache.brooklyn.util.stream.KnownSizeInputStream
diff --git a/core/src/test/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasksTest.java b/core/src/test/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasksTest.java
index 0e22470ea0..327fe74152 100644
--- a/core/src/test/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasksTest.java
+++ b/core/src/test/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasksTest.java
@@ -35,6 +35,7 @@ import org.apache.brooklyn.util.core.task.ssh.SshPutTaskWrapper;
 import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
 import org.apache.brooklyn.util.exceptions.PropagatedRuntimeException;
 import org.apache.brooklyn.util.net.Urls;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.commons.io.FileUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -188,9 +189,9 @@ public class SshEffectorTasksTest {
     public void testRunningPidFromFile() throws IOException {
         File f = File.createTempFile("testBrooklynPid", ".pid");
         Files.write( (""+getMyPid()).getBytes(), f );
-        ProcessTaskWrapper<Integer> t = submit(SshEffectorTasks.codePidFromFileRunning(f.getPath()));
+        ProcessTaskWrapper<Integer> t = submit(SshEffectorTasks.codePidFromFileRunning(BashCommandsConfigurable.newInstance(), f.getPath()));
         Assert.assertEquals(t.getTask().getUnchecked(), (Integer)0);
-        ProcessTaskWrapper<Boolean> t2 = submit(SshEffectorTasks.isPidFromFileRunning(f.getPath()));
+        ProcessTaskWrapper<Boolean> t2 = submit(SshEffectorTasks.isPidFromFileRunning(BashCommandsConfigurable.newInstance(), f.getPath()));
         Assert.assertTrue(t2.getTask().getUnchecked());
     }
 
diff --git a/core/src/test/java/org/apache/brooklyn/util/core/ssh/BashCommandsIntegrationTest.java b/core/src/test/java/org/apache/brooklyn/util/core/ssh/BashCommandsIntegrationTest.java
index 311bce7d38..d6699ced71 100644
--- a/core/src/test/java/org/apache/brooklyn/util/core/ssh/BashCommandsIntegrationTest.java
+++ b/core/src/test/java/org/apache/brooklyn/util/core/ssh/BashCommandsIntegrationTest.java
@@ -18,23 +18,14 @@
  */
 package org.apache.brooklyn.util.core.ssh;
 
-import static java.lang.String.format;
-import static org.apache.brooklyn.util.ssh.BashCommands.sudo;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertNotEquals;
-import static org.testng.Assert.assertTrue;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.net.ServerSocket;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.io.Files;
 import org.apache.brooklyn.core.test.BrooklynMgmtUnitTestSupport;
 import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.test.DisableOnWindows;
 import org.apache.brooklyn.util.core.ResourceUtils;
 import org.apache.brooklyn.util.core.task.BasicExecutionContext;
@@ -43,7 +34,7 @@ import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
 import org.apache.brooklyn.util.javalang.JavaClassNames;
 import org.apache.brooklyn.util.net.Networking;
 import org.apache.brooklyn.util.os.Os;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.text.Identifiers;
 import org.apache.brooklyn.util.text.Strings;
 import org.apache.brooklyn.util.time.Duration;
@@ -54,13 +45,17 @@ import org.testng.Assert;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
-import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
-import org.apache.brooklyn.location.ssh.SshMachineLocation;
 
-import com.google.common.base.Charsets;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.io.Files;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import static java.lang.String.format;
+import static org.testng.Assert.*;
 
 public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
 
@@ -83,7 +78,9 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     private File localRepoEntityBasePath;
     private String localRepoEntityVersionPath;
     private File localRepoEntityFile;
-    
+
+    private final BashCommandsConfigurable bashTestInstance = BashCommandsConfigurable.newInstance();
+
     @BeforeMethod(alwaysRun=true)
     public void setUp() throws Exception {
         super.setUp();
@@ -136,7 +133,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     @Test(groups="Integration")
     @DisableOnWindows(reason = "Needs a bash shell available on localhost")
     public void testRemoveRequireTtyFromSudoersFile() throws Exception {
-        String cmds = BashCommands.dontRequireTtyForSudo();
+        String cmds = bashTestInstance.dontRequireTtyForSudo();
 
         
         ByteArrayOutputStream outStream = new ByteArrayOutputStream();
@@ -160,7 +157,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     public void testSudo() throws Exception {
         ByteArrayOutputStream outStream = new ByteArrayOutputStream();
         ByteArrayOutputStream errStream = new ByteArrayOutputStream();
-        String cmd = sudo("whoami");
+        String cmd = bashTestInstance.sudo("whoami");
         int exitcode = loc.execCommands(ImmutableMap.of("out", outStream, "err", errStream), "test", ImmutableList.of(cmd));
         String outstr = new String(outStream.toByteArray());
         String errstr = new String(errStream.toByteArray());
@@ -170,7 +167,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     }
     
     public void testDownloadUrl() throws Exception {
-        List<String> cmds = BashCommands.commandsToDownloadUrlsAs(
+        List<String> cmds = bashTestInstance.commandsToDownloadUrlsAs(
                 ImmutableList.of(sourceFileUrl1), 
                 destFile.getAbsolutePath());
         int exitcode = loc.execCommands("test", cmds);
@@ -182,7 +179,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     @Test(groups="Integration")
     @DisableOnWindows(reason = "Needs a bash shell available on localhost")
     public void testDownloadFirstSuccessfulFile() throws Exception {
-        List<String> cmds = BashCommands.commandsToDownloadUrlsAs(
+        List<String> cmds = bashTestInstance.commandsToDownloadUrlsAs(
                 ImmutableList.of(sourceNonExistantFileUrl, sourceFileUrl1, sourceFileUrl2), 
                 destFile.getAbsolutePath());
         int exitcode = loc.execCommands("test", cmds);
@@ -196,7 +193,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     public void testDownloadToStdout() throws Exception {
         ProcessTaskWrapper<String> t = SshTasks.newSshExecTaskFactory(loc, 
                 "cd "+destFile.getParentFile().getAbsolutePath(),
-                BashCommands.downloadToStdout(Arrays.asList(sourceFileUrl1))+" | sed s/my/your/")
+                bashTestInstance.downloadToStdout(Arrays.asList(sourceFileUrl1))+" | sed s/my/your/")
             .requiringZeroAndReturningStdout().newTask();
 
         String result = exec.submit(t).get();
@@ -207,7 +204,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     @DisableOnWindows(reason = "Needs an ssh server listening on port 22 on localhost")
     public void testAlternativesWhereFirstSucceeds() throws Exception {
         ProcessTaskWrapper<Integer> t = SshTasks.newSshExecTaskFactory(loc)
-                .add(BashCommands.alternatives(Arrays.asList("echo first", "exit 88")))
+                .add(bashTestInstance.alternatives(Arrays.asList("echo first", "exit 88")))
                 .newTask();
 
         Integer returnCode = exec.submit(t).get();
@@ -222,7 +219,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     @DisableOnWindows(reason = "Needs an ssh server listening on port 22 on localhost")
     public void testAlternatives() throws Exception {
         ProcessTaskWrapper<Integer> t = SshTasks.newSshExecTaskFactory(loc)
-                .add(BashCommands.alternatives(Arrays.asList("asdfj_no_such_command_1", "exit 88")))
+                .add(bashTestInstance.alternatives(Arrays.asList("asdfj_no_such_command_1", "exit 88")))
                 .newTask();
 
         Integer returnCode = exec.submit(t).get();
@@ -234,7 +231,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     @DisableOnWindows(reason = "Needs an ssh server listening on port 22 on localhost")
     public void testRequireTestHandlesFailure() throws Exception {
         ProcessTaskWrapper<?> t = SshTasks.newSshExecTaskFactory(loc)
-            .add(BashCommands.requireTest("-f "+sourceNonExistantFile.getPath(),
+            .add(bashTestInstance.requireTest("-f "+sourceNonExistantFile.getPath(),
                     "The requested file does not exist")).newTask();
 
         exec.submit(t).get();
@@ -247,7 +244,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     @DisableOnWindows(reason = "Needs an ssh server listening on port 22 on localhost")
     public void testRequireTestHandlesSuccess() throws Exception {
         ProcessTaskWrapper<?> t = SshTasks.newSshExecTaskFactory(loc)
-            .add(BashCommands.requireTest("-f "+sourceFile1.getPath(),
+            .add(bashTestInstance.requireTest("-f "+sourceFile1.getPath(),
                     "The requested file does not exist")).newTask();
 
         exec.submit(t).get();
@@ -259,7 +256,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     @DisableOnWindows(reason = "Needs an ssh server listening on port 22 on localhost")
     public void testRequireFileHandlesFailure() throws Exception {
         ProcessTaskWrapper<?> t = SshTasks.newSshExecTaskFactory(loc)
-            .add(BashCommands.requireFile(sourceNonExistantFile.getPath())).newTask();
+            .add(bashTestInstance.requireFile(sourceNonExistantFile.getPath())).newTask();
 
         exec.submit(t).get();
         assertNotEquals(t.getExitCode(), 0);
@@ -273,7 +270,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     @DisableOnWindows(reason = "Needs an ssh server listening on port 22 on localhost")
     public void testRequireFileHandlesSuccess() throws Exception {
         ProcessTaskWrapper<?> t = SshTasks.newSshExecTaskFactory(loc)
-            .add(BashCommands.requireFile(sourceFile1.getPath())).newTask();
+            .add(bashTestInstance.requireFile(sourceFile1.getPath())).newTask();
 
         exec.submit(t).get();
         assertEquals(t.getExitCode(), (Integer)0);
@@ -284,7 +281,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     @DisableOnWindows(reason = "Needs an ssh server listening on port 22 on localhost")
     public void testRequireFailureExitsImmediately() throws Exception {
         ProcessTaskWrapper<?> t = SshTasks.newSshExecTaskFactory(loc)
-            .add(BashCommands.requireTest("-f "+sourceNonExistantFile.getPath(),
+            .add(bashTestInstance.requireTest("-f "+sourceNonExistantFile.getPath(),
                     "The requested file does not exist"))
             .add("echo shouldnae come here").newTask();
 
@@ -299,7 +296,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     @DisableOnWindows(reason = "Needs a bash shell available on localhost")
     public void testPipeMultiline() throws Exception {
         String output = execRequiringZeroAndReturningStdout(loc,
-                BashCommands.pipeTextTo("hello world\n"+"and goodbye\n", "wc")).get();
+                bashTestInstance.pipeTextTo("hello world\n"+"and goodbye\n", "wc")).get();
 
         assertEquals(Strings.replaceAllRegex(output, "\\s+", " ").trim(), "3 4 25");
     }
@@ -308,7 +305,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     @DisableOnWindows(reason = "Needs a bash shell available on localhost")
     public void testWaitForFileContentsWhenAbortingOnFail() throws Exception {
         String fileContent = "mycontents";
-        String cmd = BashCommands.waitForFileContents(destFile.getAbsolutePath(), fileContent, Duration.ONE_SECOND, true);
+        String cmd = bashTestInstance.waitForFileContents(destFile.getAbsolutePath(), fileContent, Duration.ONE_SECOND, true);
 
         int exitcode = loc.execCommands("test", ImmutableList.of(cmd));
         assertEquals(exitcode, 1);
@@ -322,7 +319,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     @DisableOnWindows(reason = "Needs a bash shell available on localhost")
     public void testWaitForFileContentsWhenNotAbortingOnFail() throws Exception {
         String fileContent = "mycontents";
-        String cmd = BashCommands.waitForFileContents(destFile.getAbsolutePath(), fileContent, Duration.ONE_SECOND, false);
+        String cmd = bashTestInstance.waitForFileContents(destFile.getAbsolutePath(), fileContent, Duration.ONE_SECOND, false);
 
         String output = execRequiringZeroAndReturningStdout(loc, cmd).get();
         assertTrue(output.contains("Couldn't find"), "output="+output);
@@ -337,7 +334,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     public void testWaitForFileContentsWhenContentsAppearAfterStart() throws Exception {
         String fileContent = "mycontents";
 
-        String cmd = BashCommands.waitForFileContents(destFile.getAbsolutePath(), fileContent, Duration.THIRTY_SECONDS, false);
+        String cmd = bashTestInstance.waitForFileContents(destFile.getAbsolutePath(), fileContent, Duration.THIRTY_SECONDS, false);
         ProcessTaskWrapper<String> t = execRequiringZeroAndReturningStdout(loc, cmd);
         exec.submit(t);
         
@@ -353,7 +350,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     @Test(groups="Integration")
     @DisableOnWindows(reason = "Needs a bash shell available on localhost")
     public void testWaitForFileExistsWhenAbortingOnFail() throws Exception {
-        String cmd = BashCommands.waitForFileExists(destFile.getAbsolutePath(), Duration.ONE_SECOND, true);
+        String cmd = bashTestInstance.waitForFileExists(destFile.getAbsolutePath(), Duration.ONE_SECOND, true);
 
         int exitcode = loc.execCommands("test", ImmutableList.of(cmd));
         assertEquals(exitcode, 0);
@@ -366,7 +363,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     @Test(groups="Integration")
     @DisableOnWindows(reason = "Needs a bash shell available on localhost")
     public void testWaitForFileExistsWhenNotAbortingOnFail() throws Exception {
-        String cmd = BashCommands.waitForFileExists(destFile.getAbsolutePath(), Duration.ONE_SECOND, false);
+        String cmd = bashTestInstance.waitForFileExists(destFile.getAbsolutePath(), Duration.ONE_SECOND, false);
 
         String output = execRequiringZeroAndReturningStdout(loc, cmd).get();
         assertFalse(output.contains("Couldn't find"), "output="+output);
@@ -382,7 +379,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
         ServerSocket serverSocket = openServerSocket();
         try {
             int port = serverSocket.getLocalPort();
-            String cmd = BashCommands.waitForPortFree(port, Duration.ONE_SECOND, true);
+            String cmd = bashTestInstance.waitForPortFree(port, Duration.ONE_SECOND, true);
     
             int exitcode = loc.execCommands("test", ImmutableList.of(cmd));
             assertEquals(exitcode, 1);
@@ -402,7 +399,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
         ServerSocket serverSocket = openServerSocket();
         try {
             int port = serverSocket.getLocalPort();
-            String cmd = BashCommands.waitForPortFree(port, Duration.ONE_SECOND, false);
+            String cmd = bashTestInstance.waitForPortFree(port, Duration.ONE_SECOND, false);
     
             String output = execRequiringZeroAndReturningStdout(loc, cmd).get();
             assertTrue(output.contains(port+" still in use"), "output="+output);
@@ -423,7 +420,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
         try {
             int port = serverSocket.getLocalPort();
     
-            String cmd = BashCommands.waitForPortFree(port, Duration.THIRTY_SECONDS, false);
+            String cmd = bashTestInstance.waitForPortFree(port, Duration.THIRTY_SECONDS, false);
             ProcessTaskWrapper<String> t = execRequiringZeroAndReturningStdout(loc, cmd);
             exec.submit(t);
             
@@ -470,15 +467,15 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
         LocalManagementContextForTests mgmt = new LocalManagementContextForTests();
         SshMachineLocation loc = mgmt.getLocationManager().createLocation(LocalhostMachineProvisioningLocation.spec()).obtain();
 
-        execRequiringZeroAndReturningStdout(loc, sudo("cp /etc/hosts /etc/hosts-orig-testSetHostname")).get();
-        execRequiringZeroAndReturningStdout(loc, BashCommands.ifFileExistsElse0("/etc/hostname", sudo("cp /etc/hostname /etc/hostname-orig-testSetHostname"))).get();
-        execRequiringZeroAndReturningStdout(loc, BashCommands.ifFileExistsElse0("/etc/sysconfig/network", sudo("cp /etc/sysconfig/network /etc/sysconfig/network-orig-testSetHostname"))).get();
+        execRequiringZeroAndReturningStdout(loc, bashTestInstance.sudo("cp /etc/hosts /etc/hosts-orig-testSetHostname")).get();
+        execRequiringZeroAndReturningStdout(loc, bashTestInstance.ifFileExistsElse0("/etc/hostname", bashTestInstance.sudo("cp /etc/hostname /etc/hostname-orig-testSetHostname"))).get();
+        execRequiringZeroAndReturningStdout(loc, bashTestInstance.ifFileExistsElse0("/etc/sysconfig/network", bashTestInstance.sudo("cp /etc/sysconfig/network /etc/sysconfig/network-orig-testSetHostname"))).get();
         
         String origHostname = getHostnameNoArgs(loc);
         assertTrue(Strings.isNonBlank(origHostname));
         
         try {
-            List<String> cmd = (includeDomain) ? BashCommands.setHostname(newHostname, newDomain) : BashCommands.setHostname(newHostname);
+            List<String> cmd = (includeDomain) ? bashTestInstance.setHostname(newHostname, newDomain) : bashTestInstance.setHostname(newHostname);
             execRequiringZeroAndReturningStdout(loc, cmd).get();
 
             String actualHostnameUnqualified = getHostnameUnqualified(loc);
@@ -500,10 +497,10 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
             log.info("result="+result);
             
         } finally {
-            execRequiringZeroAndReturningStdout(loc, sudo("cp /etc/hosts-orig-testSetHostname /etc/hosts")).get();
-            execRequiringZeroAndReturningStdout(loc, BashCommands.ifFileExistsElse0("/etc/hostname-orig-testSetHostname", sudo("cp /etc/hostname-orig-testSetHostname /etc/hostname"))).get();
-            execRequiringZeroAndReturningStdout(loc, BashCommands.ifFileExistsElse0("/etc/sysconfig/network-orig-testSetHostname", sudo("cp /etc/sysconfig/network-orig-testSetHostname /etc/sysconfig/network"))).get();
-            execRequiringZeroAndReturningStdout(loc, sudo("hostname "+origHostname)).get();
+            execRequiringZeroAndReturningStdout(loc, bashTestInstance.sudo("cp /etc/hosts-orig-testSetHostname /etc/hosts")).get();
+            execRequiringZeroAndReturningStdout(loc, bashTestInstance.ifFileExistsElse0("/etc/hostname-orig-testSetHostname", bashTestInstance.sudo("cp /etc/hostname-orig-testSetHostname /etc/hostname"))).get();
+            execRequiringZeroAndReturningStdout(loc, bashTestInstance.ifFileExistsElse0("/etc/sysconfig/network-orig-testSetHostname", bashTestInstance.sudo("cp /etc/sysconfig/network-orig-testSetHostname /etc/sysconfig/network"))).get();
+            execRequiringZeroAndReturningStdout(loc, bashTestInstance.sudo("hostname "+origHostname)).get();
         }
     }
 
@@ -513,14 +510,14 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
         LocalManagementContextForTests mgmt = new LocalManagementContextForTests();
         SshMachineLocation loc = mgmt.getLocationManager().createLocation(LocalhostMachineProvisioningLocation.spec()).obtain();
 
-        execRequiringZeroAndReturningStdout(loc, sudo("cp /etc/hosts /etc/hosts-orig-testModifyEtcHosts")).get();
+        execRequiringZeroAndReturningStdout(loc, bashTestInstance.sudo("cp /etc/hosts /etc/hosts-orig-testModifyEtcHosts")).get();
         int numLinesOrig = Integer.parseInt(execRequiringZeroAndReturningStdout(loc, "wc -l /etc/hosts").get().trim().split("\\s")[0]);
         
         try {
-            String cmd = BashCommands.prependToEtcHosts("1.2.3.4", "myhostnamefor1234.at.start", "myhostnamefor1234b");
+            String cmd = bashTestInstance.prependToEtcHosts("1.2.3.4", "myhostnamefor1234.at.start", "myhostnamefor1234b");
             execRequiringZeroAndReturningStdout(loc, cmd).get();
             
-            String cmd2 = BashCommands.appendToEtcHosts("5.6.7.8", "myhostnamefor5678.at.end", "myhostnamefor5678");
+            String cmd2 = bashTestInstance.appendToEtcHosts("5.6.7.8", "myhostnamefor5678.at.end", "myhostnamefor5678");
             execRequiringZeroAndReturningStdout(loc, cmd2).get();
             
             String grepFirst = execRequiringZeroAndReturningStdout(loc, "grep -n myhostnamefor1234 /etc/hosts").get();
@@ -532,7 +529,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
             assertTrue(grepLast.startsWith((numLinesOrig+2)+":") && grepLast.contains("5.6.7.8 myhostnamefor5678.at.end myhostnamefor5678"), "last="+grepLast);
             assertEquals(numLinesOrig + 2, numLinesAfter, "lines orig="+numLinesOrig+", after="+numLinesAfter);
         } finally {
-            execRequiringZeroAndReturningStdout(loc, sudo("cp /etc/hosts-orig-testModifyEtcHosts /etc/hosts")).get();
+            execRequiringZeroAndReturningStdout(loc, bashTestInstance.sudo("cp /etc/hosts-orig-testModifyEtcHosts /etc/hosts")).get();
         }
     }
     
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
index 7b1b4be93d..764b9021e1 100644
--- a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
@@ -26,7 +26,6 @@ import org.apache.brooklyn.core.location.*;
 import org.apache.brooklyn.core.location.MachineLifecycleUtils.MachineStatus;
 import static org.apache.brooklyn.util.JavaGroovyEquivalents.elvis;
 import static org.apache.brooklyn.util.JavaGroovyEquivalents.groovyTruth;
-import static org.apache.brooklyn.util.ssh.BashCommands.sbinPath;
 import static org.jclouds.compute.predicates.NodePredicates.withIds;
 import static org.jclouds.util.Throwables2.getFirstThrowableOfType;
 
@@ -90,6 +89,7 @@ import org.apache.brooklyn.util.core.ClassLoaderUtils;
 import org.apache.brooklyn.util.core.ResourceUtils;
 import org.apache.brooklyn.util.core.config.ConfigBag;
 import org.apache.brooklyn.util.core.config.ResolvingConfigBag;
+import org.apache.brooklyn.util.core.file.BrooklynOsCommands;
 import org.apache.brooklyn.util.core.flags.SetFromFlag;
 import org.apache.brooklyn.util.core.internal.ssh.ShellTool;
 import org.apache.brooklyn.util.core.internal.ssh.SshTool;
@@ -112,10 +112,10 @@ import org.apache.brooklyn.util.net.Cidr;
 import org.apache.brooklyn.util.net.Networking;
 import org.apache.brooklyn.util.net.Protocol;
 import org.apache.brooklyn.util.repeat.Repeater;
-import org.apache.brooklyn.util.ssh.BashCommands;
-import org.apache.brooklyn.util.ssh.IptablesCommands;
-import org.apache.brooklyn.util.ssh.IptablesCommands.Chain;
-import org.apache.brooklyn.util.ssh.IptablesCommands.Policy;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
+import org.apache.brooklyn.util.ssh.IptablesCommandsConfigurable;
+import org.apache.brooklyn.util.ssh.IptablesCommandsConfigurable.Chain;
+import org.apache.brooklyn.util.ssh.IptablesCommandsConfigurable.Policy;
 import org.apache.brooklyn.util.stream.Streams;
 import org.apache.brooklyn.util.text.KeyValueParser;
 import org.apache.brooklyn.util.text.StringPredicates;
@@ -404,9 +404,12 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                 : (OsFamily.WINDOWS == confFamily);
     }
 
+    private BashCommandsConfigurable bashCommands() { return BrooklynOsCommands.bash(getManagementContext()); }
+    private IptablesCommandsConfigurable iptablesCommands() { return new IptablesCommandsConfigurable(bashCommands()); }
+
     public boolean isLocationFirewalldEnabled(SshMachineLocation location) {
         int result = location.execCommands("checking if firewalld is active",
-                ImmutableList.of(IptablesCommands.firewalldServiceIsActive()));
+                ImmutableList.of(iptablesCommands().firewalldServiceIsActive()));
         return result == 0;
     }
 
@@ -939,8 +942,8 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                                 (SshMachineLocation)machineLocation,
                                 "using urandom instead of random",
                                 Arrays.asList(
-                                        BashCommands.sudo("mv /dev/random /dev/random-real"),
-                                        BashCommands.sudo("ln -s /dev/urandom /dev/random")));
+                                        bashCommands().sudo("mv /dev/random /dev/random-real"),
+                                        bashCommands().sudo("ln -s /dev/urandom /dev/random")));
                     }
                 }
 
@@ -955,10 +958,10 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                         executeCommandThrowingOnError(
                                 (SshMachineLocation)machineLocation,
                                 "Generate hostname " + node.getName(),
-                                ImmutableList.of(BashCommands.chainGroup(
-                                        String.format("echo '127.0.0.1 %s' | ( %s )", node.getName(), BashCommands.sudo("tee -a /etc/hosts")),
-                                        "{ " + BashCommands.sudo("sed -i \"s/HOSTNAME=.*/HOSTNAME=" + node.getName() + "/g\" /etc/sysconfig/network") + " || true ; }",
-                                        BashCommands.sudo("hostname " + node.getName()))));
+                                ImmutableList.of(bashCommands().chainGroup(
+                                        String.format("echo '127.0.0.1 %s' | ( %s )", node.getName(), bashCommands().sudo("tee -a /etc/hosts")),
+                                        "{ " + bashCommands().sudo("sed -i \"s/HOSTNAME=.*/HOSTNAME=" + node.getName() + "/g\" /etc/sysconfig/network") + " || true ; }",
+                                        bashCommands().sudo("hostname " + node.getName()))));
                     }
                 }
 
@@ -979,14 +982,14 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
 
                             if (isLocationFirewalldEnabled((SshMachineLocation)machineLocation)) {
                                 for (Integer port : inboundPorts) {
-                                    iptablesRules.add(IptablesCommands.addFirewalldRule(Chain.INPUT, Protocol.TCP, port, Policy.ACCEPT));
+                                    iptablesRules.add(iptablesCommands().addFirewalldRule(Chain.INPUT, Protocol.TCP, port, Policy.ACCEPT));
                                  }
                             } else {
                                 iptablesRules = Lists.newArrayList();
                                 for (Integer port : inboundPorts) {
-                                   iptablesRules.add(IptablesCommands.insertIptablesRule(Chain.INPUT, Protocol.TCP, port, Policy.ACCEPT));
+                                   iptablesRules.add(iptablesCommands().insertIptablesRule(Chain.INPUT, Protocol.TCP, port, Policy.ACCEPT));
                                 }
-                                iptablesRules.add(IptablesCommands.saveIptablesRules());
+                                iptablesRules.add(iptablesCommands().saveIptablesRules());
                             }
                             List<String> batch = Lists.newArrayList();
                             // Some entities, such as Riak (erlang based) have a huge range of ports, which leads to a script that
@@ -1010,7 +1013,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                             executeCommandWarningOnError(
                                     (SshMachineLocation)machineLocation,
                                     "List iptables rules",
-                                    ImmutableList.of(IptablesCommands.listIptablesRule()));
+                                    ImmutableList.of(iptablesCommands().listIptablesRule()));
                         }
                     }
                 }
@@ -1025,9 +1028,9 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
 
                         List<String> cmds = ImmutableList.<String>of();
                         if (isLocationFirewalldEnabled((SshMachineLocation)machineLocation)) {
-                            cmds = ImmutableList.of(IptablesCommands.firewalldServiceStop(), IptablesCommands.firewalldServiceStatus());
+                            cmds = ImmutableList.of(iptablesCommands().firewalldServiceStop(), iptablesCommands().firewalldServiceStatus());
                         } else {
-                            cmds = ImmutableList.of(IptablesCommands.iptablesServiceStop(), IptablesCommands.iptablesServiceStatus());
+                            cmds = ImmutableList.of(iptablesCommands().iptablesServiceStop(), iptablesCommands().iptablesServiceStatus());
                         }
                         executeCommandWarningOnError(
                                 (SshMachineLocation)machineLocation,
@@ -1825,7 +1828,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                 SshMachineLocation sshLoc = createTemporarySshMachineLocation(managementHostAndPort, initialCredentials, config);
                 try {
                     // BROOKLYN-188: for SUSE, need to specify the path (for groupadd, useradd, etc)
-                    Map<String, ?> env = ImmutableMap.of("PATH", sbinPath());
+                    Map<String, ?> env = ImmutableMap.of("PATH", bashCommands().sbinPath());
 
                     int exitcode = sshLoc.execScript(execProps, "create-user", commands, env);
 
@@ -3067,7 +3070,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                     MutableMap.of("out", outStream, "err", errStream),
                     "get public AWS hostname",
                     ImmutableList.of(
-                            BashCommands.INSTALL_CURL,
+                            bashCommands().INSTALL_CURL,
                             "echo `curl --silent --retry 20 http://169.254.169.254/latest/meta-data/public-hostname`; exit"));
             String outString = new String(outStream.toByteArray());
             String[] outLines = outString.split("\n");
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/policy/jclouds/os/CreateUserPolicy.java b/locations/jclouds/src/main/java/org/apache/brooklyn/policy/jclouds/os/CreateUserPolicy.java
index 555d7cb866..abf0c8d8a0 100644
--- a/locations/jclouds/src/main/java/org/apache/brooklyn/policy/jclouds/os/CreateUserPolicy.java
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/policy/jclouds/os/CreateUserPolicy.java
@@ -29,13 +29,13 @@ import org.apache.brooklyn.api.sensor.SensorEventListener;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
 import org.apache.brooklyn.core.entity.AbstractEntity;
-import org.apache.brooklyn.core.entity.EntityInternal;
 import org.apache.brooklyn.core.policy.AbstractPolicy;
 import org.apache.brooklyn.core.sensor.Sensors;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.util.core.file.BrooklynOsCommands;
 import org.apache.brooklyn.util.core.flags.SetFromFlag;
 import org.apache.brooklyn.util.core.internal.ssh.SshTool;
-import static org.apache.brooklyn.util.ssh.BashCommands.sbinPath;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.text.Identifiers;
 import org.jclouds.compute.config.AdminAccessConfiguration;
 import org.jclouds.scriptbuilder.functions.InitAdminAccess;
@@ -149,14 +149,15 @@ public class CreateUserPolicy extends AbstractPolicy implements SensorEventListe
         String cmd = adminAccess.render(scriptOsFamily);
 
         // Exec command to create the user
-        int result = machine.execScript(ImmutableMap.of(SshTool.PROP_RUN_AS_ROOT.getName(), true), "create-user-"+user, ImmutableList.of(cmd), ImmutableMap.of("PATH", sbinPath()));
+        BashCommandsConfigurable bash = BrooklynOsCommands.bash(entity);
+        int result = machine.execScript(ImmutableMap.of(SshTool.PROP_RUN_AS_ROOT.getName(), true), "create-user-"+user, ImmutableList.of(cmd), ImmutableMap.of("PATH", bash.sbinPath()));
         if (result != 0) {
             throw new IllegalStateException("Failed to auto-generate user, using command "+cmd);
         }
 
         // Exec command to grant password-access to sshd (which may have been disabled earlier).
         cmd = new SshdConfig(ImmutableMap.of("PasswordAuthentication", "yes")).render(scriptOsFamily);
-        result = machine.execScript(ImmutableMap.of(SshTool.PROP_RUN_AS_ROOT.getName(), true), "create-user-"+user, ImmutableList.of(cmd), ImmutableMap.of("PATH", sbinPath()));
+        result = machine.execScript(ImmutableMap.of(SshTool.PROP_RUN_AS_ROOT.getName(), true), "create-user-"+user, ImmutableList.of(cmd), ImmutableMap.of("PATH", bash.sbinPath()));
         if (result != 0) {
             throw new IllegalStateException("Failed to enable ssh-login-with-password, using command "+cmd);
         }
@@ -168,7 +169,7 @@ public class CreateUserPolicy extends AbstractPolicy implements SensorEventListe
                             user+" ALL = (ALL) NOPASSWD:ALL\n"+
                             "END_OF_JCLOUDS_FILE\n",
                     "chmod 0440 /etc/sudoers");
-            result = machine.execScript(ImmutableMap.of(SshTool.PROP_RUN_AS_ROOT.getName(), true), "add-user-to-sudoers-"+user, cmds, ImmutableMap.of("PATH", sbinPath()));
+            result = machine.execScript(ImmutableMap.of(SshTool.PROP_RUN_AS_ROOT.getName(), true), "add-user-to-sudoers-"+user, cmds, ImmutableMap.of("PATH", bash.sbinPath()));
             if (result != 0) {
                 throw new IllegalStateException("Failed to auto-generate user, using command "+cmds);
             }
diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsSuseLiveTest.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsSuseLiveTest.java
index 8fe3dc2e6d..b36dbb9b39 100644
--- a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsSuseLiveTest.java
+++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsSuseLiveTest.java
@@ -25,7 +25,7 @@ import org.apache.brooklyn.core.location.Locations;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.os.Os;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.stream.Streams;
 import org.testng.annotations.Test;
 
@@ -36,7 +36,7 @@ import com.google.common.collect.ImmutableMap;
  * Extra-special tests for deploying SUSE VMs, because we've had so many problems. For example:
  * <ul>
  *   <li>{@code groupadd -f}:  You are using an undocumented option (-f); and exits with 9
- *   <li>path does not by default contain groupadd etc (see {@link BashCommands#sbinPath()}
+ *   <li>path does not by default contain groupadd etc (see {@link BashCommandsConfigurable#sbinPath()}
  * </ul>
  */
 public class JcloudsSuseLiveTest extends AbstractJcloudsLiveTest {
diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/SimpleJcloudsLocationUserLoginAndConfigLiveTest.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/SimpleJcloudsLocationUserLoginAndConfigLiveTest.java
index e9755ad92b..230bc78f4a 100644
--- a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/SimpleJcloudsLocationUserLoginAndConfigLiveTest.java
+++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/SimpleJcloudsLocationUserLoginAndConfigLiveTest.java
@@ -26,7 +26,8 @@ import java.util.Map;
 import org.apache.brooklyn.api.location.NoMachinesAvailableException;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.util.collections.MutableMap;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.core.file.BrooklynOsCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.text.Identifiers;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -214,7 +215,7 @@ public class SimpleJcloudsLocationUserLoginAndConfigLiveTest extends AbstractJcl
                 "grantUserSudo", false,
                 "waitForSshable", 30*1000));
 
-        int exitCode = execWithExitCode(m, ImmutableList.of(BashCommands.sudo("echo yes")));
+        int exitCode = execWithExitCode(m, ImmutableList.of(BrooklynOsCommands.bash(m.getManagementContext()).sudo("echo yes")));
         Assert.assertFalse(exitCode == 0, "exit code for sudo command should not have been 0");
     }
 
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNode.java b/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNode.java
index 027085f6cf..3fb424d779 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNode.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNode.java
@@ -45,7 +45,7 @@ import org.apache.brooklyn.entity.software.base.SoftwareProcess;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.core.flags.SetFromFlag;
 import org.apache.brooklyn.util.net.Networking;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.time.Duration;
 
 import com.google.common.annotations.VisibleForTesting;
@@ -99,7 +99,7 @@ public interface BrooklynNode extends SoftwareProcess, UsesJava {
     ConfigKey<String> MANAGEMENT_PASSWORD =
             ConfigKeys.newStringConfigKey("brooklynnode.managementPassword", "Password for MANAGEMENT_USER", null);
 
-    /** useful e.g. with {@link BashCommands#generateKeyInDotSshIdRsaIfNotThere() } */
+    /** useful e.g. with {@link BashCommandsConfigurable#generateKeyInDotSshIdRsaIfNotThere() } */
     @SetFromFlag("extraCustomizationScript")
     ConfigKey<String> EXTRA_CUSTOMIZATION_SCRIPT = ConfigKeys.newStringConfigKey("brooklynnode.customization.extraScript",
         "Optional additional script commands to run as part of customization; this might e.g. ensure id_rsa is set up",
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java b/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
index 80e717e199..67a0d8689e 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
@@ -38,12 +38,13 @@ import org.apache.brooklyn.util.collections.MutableList;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.core.file.ArchiveBuilder;
 import org.apache.brooklyn.util.core.file.ArchiveUtils;
+import org.apache.brooklyn.util.core.file.BrooklynOsCommands;
 import org.apache.brooklyn.util.core.internal.ssh.SshTool;
 import org.apache.brooklyn.util.core.task.DynamicTasks;
 import org.apache.brooklyn.util.net.Networking;
 import org.apache.brooklyn.util.net.Urls;
 import org.apache.brooklyn.util.os.Os;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.text.Identifiers;
 import org.apache.brooklyn.util.text.Strings;
 import org.slf4j.Logger;
@@ -153,9 +154,9 @@ public class BrooklynNodeSshDriver extends JavaSoftwareProcessSshDriver implemen
                 getMachine().copyTo(distroStream, getInstallDir()+"/"+saveAs);
             }
         } else {
-            commands.addAll(BashCommands.commandsToDownloadUrlsAs(urls, saveAs));
+            commands.addAll(BrooklynOsCommands.bash(getEntity()).commandsToDownloadUrlsAs(urls, saveAs));
         }
-        commands.add(BashCommands.INSTALL_TAR);
+        commands.add(BrooklynOsCommands.bash(getEntity()).INSTALL_TAR);
         commands.add("tar xzfv " + saveAs);
         
         newScript(INSTALLING).
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaSoftwareProcessSshDriver.java b/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaSoftwareProcessSshDriver.java
index 7e0d1c2b0c..636d1d35e3 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaSoftwareProcessSshDriver.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaSoftwareProcessSshDriver.java
@@ -34,6 +34,7 @@ import org.apache.brooklyn.core.entity.Attributes;
 import org.apache.brooklyn.core.entity.Entities;
 import org.apache.brooklyn.core.entity.EntityInternal;
 import org.apache.brooklyn.entity.software.base.AbstractSoftwareProcessSshDriver;
+import org.apache.brooklyn.util.core.file.BrooklynOsCommands;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -59,7 +60,7 @@ import org.apache.brooklyn.util.core.task.ssh.SshTasks;
 import org.apache.brooklyn.util.core.task.system.ProcessTaskFactory;
 import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
 import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.text.Strings;
 import org.apache.brooklyn.util.text.StringEscapes.BashStringEscapes;
 
@@ -291,7 +292,7 @@ public abstract class JavaSoftwareProcessSshDriver extends AbstractSoftwareProce
                 return true;
             }
         }
-        return tryJavaInstall(requiredVersion, BashCommands.installJava(requiredJavaMinor)) == 0;
+        return tryJavaInstall(requiredVersion, BrooklynOsCommands.bash(getEntity()).installJava(requiredJavaMinor)) == 0;
     }
 
     /**
@@ -416,7 +417,7 @@ public abstract class JavaSoftwareProcessSshDriver extends AbstractSoftwareProce
                     // http://mail.openjdk.java.net/pipermail/net-dev/2012-July/004603.html
                     String newHostname = "br-"+getEntity().getId().toLowerCase();
                     log.info("Detected likelihood of Java hostname bug with hostname length "+len+" for "+getEntity()+"; renaming "+getMachine()+"  to hostname "+newHostname);
-                    DynamicTasks.queue(SshEffectorTasks.ssh(BashCommands.setHostname(newHostname, null))).block();
+                    DynamicTasks.queue(SshEffectorTasks.ssh(BrooklynOsCommands.bash(getEntity()).setHostname(newHostname, null))).block();
                 }
             } else {
                 log.debug("Hostname length could not be determined for location "+EffectorTasks.findSshMachine()+"; not doing Java hostname bug check");
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/machine/MachineInitTasks.java b/software/base/src/main/java/org/apache/brooklyn/entity/machine/MachineInitTasks.java
index 1ded4208de..c1bbe1e63a 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/machine/MachineInitTasks.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/machine/MachineInitTasks.java
@@ -23,6 +23,7 @@ import java.util.List;
 import org.apache.brooklyn.api.mgmt.Task;
 import org.apache.brooklyn.core.entity.EntityInternal;
 import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
+import org.apache.brooklyn.util.core.file.BrooklynOsCommands;
 import org.apache.brooklyn.util.core.task.system.ProcessTaskFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -37,10 +38,10 @@ import org.apache.brooklyn.util.core.task.DynamicTasks;
 import org.apache.brooklyn.util.core.task.Tasks;
 import org.apache.brooklyn.util.core.task.ssh.SshTasks;
 import org.apache.brooklyn.util.net.Protocol;
-import org.apache.brooklyn.util.ssh.BashCommands;
-import org.apache.brooklyn.util.ssh.IptablesCommands;
-import org.apache.brooklyn.util.ssh.IptablesCommands.Chain;
-import org.apache.brooklyn.util.ssh.IptablesCommands.Policy;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
+import org.apache.brooklyn.util.ssh.IptablesCommandsConfigurable;
+import org.apache.brooklyn.util.ssh.IptablesCommandsConfigurable.Chain;
+import org.apache.brooklyn.util.ssh.IptablesCommandsConfigurable.Policy;
 import org.apache.brooklyn.util.text.Strings;
 
 /**
@@ -69,6 +70,8 @@ public class MachineInitTasks {
         });
     }
 
+    private IptablesCommandsConfigurable iptablesCommands(SshMachineLocation m) { return new IptablesCommandsConfigurable(BrooklynOsCommands.bash(m.getManagementContext())); }
+
     protected void stopIptablesImpl(final SshMachineLocation machine) {
 
         log.info("Stopping iptables for {} at {}", entity(), machine);
@@ -78,9 +81,9 @@ public class MachineInitTasks {
         Task<Integer> checkFirewall = checkLocationFirewall(machine);
 
         if (checkFirewall.getUnchecked() == 0) {
-            cmds = ImmutableList.of(IptablesCommands.firewalldServiceStop(), IptablesCommands.firewalldServiceStatus());
+            cmds = ImmutableList.of(iptablesCommands(machine).firewalldServiceStop(), iptablesCommands(machine).firewalldServiceStatus());
         } else {
-            cmds = ImmutableList.of(IptablesCommands.iptablesServiceStop(), IptablesCommands.iptablesServiceStatus());
+            cmds = ImmutableList.of(iptablesCommands(machine).iptablesServiceStop(), iptablesCommands(machine).iptablesServiceStatus());
         }
 
 
@@ -89,7 +92,7 @@ public class MachineInitTasks {
 
 
     /**
-     * See docs in {@link BashCommands#dontRequireTtyForSudo()}
+     * See docs in {@link BashCommandsConfigurable#dontRequireTtyForSudo()}
      */
     public Task<Boolean> dontRequireTtyForSudoAsync(final SshMachineLocation machine) {
         return DynamicTasks.queue(SshTasks.dontRequireTtyForSudo(machine, true).newTask().asTask());
@@ -120,11 +123,11 @@ public class MachineInitTasks {
 
             if (checkFirewall.getUnchecked() == 0) {
                 for (Integer port : inboundPorts) {
-                    iptablesRules.add(IptablesCommands.addFirewalldRule(Chain.INPUT, Protocol.TCP, port, Policy.ACCEPT));
+                    iptablesRules.add(iptablesCommands(machine).addFirewalldRule(Chain.INPUT, Protocol.TCP, port, Policy.ACCEPT));
                  }
             } else {
-                iptablesRules = createIptablesRulesForNetworkInterface(inboundPorts);
-                iptablesInstallCommands = IptablesCommands.saveIptablesRules();
+                iptablesRules = createIptablesRulesForNetworkInterface(inboundPorts, machine);
+                iptablesInstallCommands = iptablesCommands(machine).saveIptablesRules();
             }
 
             insertIptablesRules(iptablesRules, iptablesInstallCommands, machine);
@@ -136,7 +139,7 @@ public class MachineInitTasks {
      * Returns a queued {@link Task} which checks if location firewall is enabled.
      */
     public Task<Integer> checkLocationFirewall(final SshMachineLocation machine) {
-        return subTaskHelperAllowingNonZeroExitCode("check if firewall is active", machine, IptablesCommands.firewalldServiceIsActive());
+        return subTaskHelperAllowingNonZeroExitCode("check if firewall is active", machine, iptablesCommands(machine).firewalldServiceIsActive());
     }
 
     /**
@@ -185,7 +188,7 @@ public class MachineInitTasks {
      * Returns a queued {@link Task} which lists the iptables rules.
      */
     private Task<Integer> listIptablesRules(final SshMachineLocation machine) {
-        return subTaskHelperRequiringZeroExitCode("list rules", machine, IptablesCommands.listIptablesRule());
+        return subTaskHelperRequiringZeroExitCode("list rules", machine, iptablesCommands(machine).listIptablesRule());
     }
 
     private Task<Integer> subTaskHelperRequiringZeroExitCode(String taskName, SshMachineLocation machine, String... comands) {
@@ -202,10 +205,10 @@ public class MachineInitTasks {
         return DynamicTasks.queue(taskFactory).asTask();
     }
     
-    private List<String> createIptablesRulesForNetworkInterface(Iterable<Integer> ports) {
+    private List<String> createIptablesRulesForNetworkInterface(Iterable<Integer> ports, SshMachineLocation machine) {
         List<String> iptablesRules = Lists.newArrayList();
         for (Integer port : ports) {
-           iptablesRules.add(IptablesCommands.insertIptablesRule(Chain.INPUT, Protocol.TCP, port, Policy.ACCEPT));
+           iptablesRules.add(iptablesCommands(machine).insertIptablesRule(Chain.INPUT, Protocol.TCP, port, Policy.ACCEPT));
         }
         return iptablesRules;
      }
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizer.java b/software/base/src/main/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizer.java
index 28f97ee5bc..128ad9b66c 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizer.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizer.java
@@ -30,11 +30,12 @@ import org.apache.brooklyn.core.effector.ssh.SshEffectorTasks;
 import org.apache.brooklyn.core.effector.ssh.SshEffectorTasks.SshEffectorTaskFactory;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.file.BrooklynOsCommands;
 import org.apache.brooklyn.util.core.task.DynamicTasks;
 import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
 import org.apache.brooklyn.util.core.text.TemplateProcessor;
 import org.apache.brooklyn.util.net.Networking;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.text.Strings;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -172,11 +173,12 @@ public class SetHostnameCustomizer extends BasicMachineLocationCustomizer {
         
         boolean hasDomain = Strings.isNonBlank(domainFixed);
         String fqdn = hasDomain ? hostName+"."+domainFixed : hostName;
-        
+
+        BashCommandsConfigurable bash = BrooklynOsCommands.bash(machine.getManagementContext());
         exec(machine, true, 
-                BashCommands.sudo(String.format("sed -i.bak -e '1i127.0.0.1 %s %s' -e '/^127.0.0.1/d' /etc/hosts", fqdn, hostName)),
-                BashCommands.sudo(String.format("sed -i.bak -e 's/^HOSTNAME=.*$/HOSTNAME=%s/' /etc/sysconfig/network", fqdn)),
-                BashCommands.sudo(String.format("hostname %s", fqdn)));
+                bash.sudo(String.format("sed -i.bak -e '1i127.0.0.1 %s %s' -e '/^127.0.0.1/d' /etc/hosts", fqdn, hostName)),
+                bash.sudo(String.format("sed -i.bak -e 's/^HOSTNAME=.*$/HOSTNAME=%s/' /etc/sysconfig/network", fqdn)),
+                bash.sudo(String.format("hostname %s", fqdn)));
 
         return hostName;
     }
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/machine/SetLimitsCustomizer.java b/software/base/src/main/java/org/apache/brooklyn/entity/machine/SetLimitsCustomizer.java
index e3997ff921..612e5f289b 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/machine/SetLimitsCustomizer.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/machine/SetLimitsCustomizer.java
@@ -19,7 +19,6 @@
 package org.apache.brooklyn.entity.machine;
 
 import static com.google.common.base.Preconditions.checkArgument;
-import static org.apache.brooklyn.util.ssh.BashCommands.sudo;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -36,6 +35,7 @@ import org.apache.brooklyn.core.objs.BasicConfigurableObject;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.util.core.task.DynamicTasks;
 import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
+import org.apache.brooklyn.util.ssh.BashCommands;
 import org.apache.brooklyn.util.text.Strings;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -122,7 +122,7 @@ public class SetLimitsCustomizer extends BasicMachineLocationCustomizer implemen
         try {
             List<String> cmds = new ArrayList<>();
             for (String content : contents) {
-                cmds.add(sudo(String.format("echo \"%s\" | tee -a %s", content, file)));
+                cmds.add(BashCommands.sudo(String.format("echo \"%s\" | tee -a %s", content, file)));
             }
             exec((SshMachineLocation)machine, true, cmds.toArray(new String[cmds.size()]));
         } catch (Exception e) {
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessSshDriver.java b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessSshDriver.java
index acf59bfd47..697fa6bdf0 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessSshDriver.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessSshDriver.java
@@ -36,22 +36,20 @@ import org.apache.brooklyn.core.effector.ssh.SshEffectorTasks;
 import org.apache.brooklyn.core.entity.Attributes;
 import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
 import org.apache.brooklyn.core.entity.Entities;
-import org.apache.brooklyn.core.entity.EntityInternal;
-import org.apache.brooklyn.core.feed.ConfigToAttributes;
 import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
 import org.apache.brooklyn.entity.software.base.lifecycle.NaiveScriptRunner;
 import org.apache.brooklyn.entity.software.base.lifecycle.ScriptHelper;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.util.core.file.BrooklynOsCommands;
 import org.apache.brooklyn.util.core.internal.ssh.SshTool;
 import org.apache.brooklyn.util.core.internal.ssh.sshj.SshjTool;
-import org.apache.brooklyn.util.core.json.ShellEnvironmentSerializer;
 import org.apache.brooklyn.util.core.mutex.WithMutexes;
 import org.apache.brooklyn.util.core.task.DynamicTasks;
 import org.apache.brooklyn.util.core.task.Tasks;
 import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.os.Os;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.stream.KnownSizeInputStream;
 import org.apache.brooklyn.util.stream.Streams;
 import org.apache.brooklyn.util.text.StringPredicates;
@@ -364,6 +362,8 @@ public abstract class AbstractSoftwareProcessSshDriver extends AbstractSoftwareP
         return result;
     }
 
+    protected BashCommandsConfigurable bashCommands() { return BrooklynOsCommands.bash(getEntity()); }
+
     public void checkNoHostnameBug() {
         try {
             ProcessTaskWrapper<Integer> hostnameTask = DynamicTasks.queue(SshEffectorTasks.ssh("echo FOREMARKER; hostname; echo AFTMARKER")).block();
@@ -373,7 +373,7 @@ public abstract class AbstractSoftwareProcessSshDriver extends AbstractSoftwareP
                 if (hostname.equals("(none)")) {
                     String newHostname = "br-"+getEntity().getId().toLowerCase();
                     log.info("Detected no-hostname bug with hostname "+hostname+" for "+getEntity()+"; renaming "+getMachine()+"  to hostname "+newHostname);
-                    DynamicTasks.queue(SshEffectorTasks.ssh(BashCommands.setHostname(newHostname, null))).block();
+                    DynamicTasks.queue(SshEffectorTasks.ssh(bashCommands().setHostname(newHostname, null))).block();
                 }
             } else {
                 log.debug("Hostname could not be determined for location "+EffectorTasks.findSshMachine()+"; not doing no-hostname bug check");
@@ -534,8 +534,8 @@ public abstract class AbstractSoftwareProcessSshDriver extends AbstractSoftwareP
                 // new way, preferred?
                 if (processOwner != null) {
                     s.body.append(
-                            BashCommands.sudoAsUser(processOwner, "test -f "+pidFile) + " || exit 1",
-                            "ps -p $(" + BashCommands.sudoAsUser(processOwner, "cat "+pidFile) + ")"
+                            bashCommands().sudoAsUser(processOwner, "test -f "+pidFile) + " || exit 1",
+                            "ps -p $(" + bashCommands().sudoAsUser(processOwner, "cat "+pidFile) + ")"
                     );
                 } else {
                     s.body.append(
@@ -566,17 +566,17 @@ public abstract class AbstractSoftwareProcessSshDriver extends AbstractSoftwareP
                         "fi",
                         "rm -f " + pidFile);
                 if (processOwner != null) {
-                    s.body.append(BashCommands.sudoAsUser(processOwner, stopCommand));
+                    s.body.append(bashCommands().sudoAsUser(processOwner, stopCommand));
                 } else {
                     s.body.append(stopCommand);
                 }
             } else if (KILLING.equals(phase)) {
                 if (processOwner != null) {
                     s.body.append(
-                            "export PID=$(" + BashCommands.sudoAsUser(processOwner, "cat "+pidFile) + ")",
+                            "export PID=$(" + bashCommands().sudoAsUser(processOwner, "cat "+pidFile) + ")",
                             "test -n \"$PID\" || exit 0",
-                            BashCommands.sudoAsUser(processOwner, "kill -9 $PID"),
-                            BashCommands.sudoAsUser(processOwner, "rm -f "+pidFile)
+                            bashCommands().sudoAsUser(processOwner, "kill -9 $PID"),
+                            bashCommands().sudoAsUser(processOwner, "rm -f "+pidFile)
                     );
                 } else {
                     s.body.append(
@@ -589,8 +589,8 @@ public abstract class AbstractSoftwareProcessSshDriver extends AbstractSoftwareP
             } else if (RESTARTING.equals(phase)) {
                 if (processOwner != null) {
                     s.footer.prepend(
-                            BashCommands.sudoAsUser(processOwner, "test -f "+pidFile) + " || exit 1",
-                            "ps -p $(" + BashCommands.sudoAsUser(processOwner, "cat "+pidFile) + ") || exit 1"
+                            bashCommands().sudoAsUser(processOwner, "test -f "+pidFile) + " || exit 1",
+                            "ps -p $(" + bashCommands().sudoAsUser(processOwner, "cat "+pidFile) + ") || exit 1"
                     );
                 } else {
                     s.footer.prepend(
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessSshDriver.java b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessSshDriver.java
index 07afb74da5..2cc3575075 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessSshDriver.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessSshDriver.java
@@ -32,10 +32,11 @@ import org.apache.brooklyn.entity.software.base.lifecycle.ScriptHelper;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.core.file.ArchiveUtils;
+import org.apache.brooklyn.util.core.file.BrooklynOsCommands;
 import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.net.Urls;
 import org.apache.brooklyn.util.os.Os;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.text.Identifiers;
 import org.apache.brooklyn.util.text.Strings;
 
@@ -90,8 +91,9 @@ public class VanillaSoftwareProcessSshDriver extends AbstractSoftwareProcessSshD
             downloadedFilename = resolver.getFilename();
 
             List<String> commands = new LinkedList<String>();
-            commands.addAll(BashCommands.commandsToDownloadUrlsAs(urls, downloadedFilename));
-            commands.addAll(ArchiveUtils.installCommands(downloadedFilename));
+            BashCommandsConfigurable bash = BrooklynOsCommands.bash(getEntity());
+            commands.addAll(bash.commandsToDownloadUrlsAs(urls, downloadedFilename));
+            commands.addAll(ArchiveUtils.installCommands(bash, downloadedFilename));
 
             int result = newScript(ImmutableMap.of(INSTALL_INCOMPLETE, true), INSTALLING)
                     .failOnNonZeroResultCode(false)
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/system_service/InitdServiceInstaller.java b/software/base/src/main/java/org/apache/brooklyn/entity/system_service/InitdServiceInstaller.java
index 97d51213ef..6c47b9b20b 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/system_service/InitdServiceInstaller.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/system_service/InitdServiceInstaller.java
@@ -37,13 +37,14 @@ import org.apache.brooklyn.entity.software.base.SoftwareProcess;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.core.ResourceUtils;
+import org.apache.brooklyn.util.core.file.BrooklynOsCommands;
 import org.apache.brooklyn.util.core.task.Tasks;
 import org.apache.brooklyn.util.core.task.ssh.SshPutTaskWrapper;
 import org.apache.brooklyn.util.core.task.ssh.SshTasks;
 import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
 import org.apache.brooklyn.util.core.text.TemplateProcessor;
 import org.apache.brooklyn.util.os.Os;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 
 
 public class InitdServiceInstaller implements SystemServiceInstaller {
@@ -79,12 +80,13 @@ public class InitdServiceInstaller implements SystemServiceInstaller {
         SshPutTaskWrapper putServiceTask = SshTasks.newSshPutTaskFactory(sshMachine, tmpServicePath)
                 .contents(service)
                 .newTask();
+        BashCommandsConfigurable bash = BrooklynOsCommands.bash(sshMachine.getManagementContext());
         ProcessTaskWrapper<Integer> installServiceTask = SshTasks.newSshExecTaskFactory(sshMachine,
-                BashCommands.chain(
-                    BashCommands.sudo("mv " + tmpServicePath + " " + servicePath),
-                    BashCommands.sudo("chmod 0755 " + servicePath),
-                    BashCommands.sudo("chkconfig --add " + serviceName),
-                    BashCommands.sudo("chkconfig " + serviceName + " on")))
+                bash.chain(
+                    bash.sudo("mv " + tmpServicePath + " " + servicePath),
+                    bash.sudo("chmod 0755 " + servicePath),
+                    bash.sudo("chkconfig --add " + serviceName),
+                    bash.sudo("chkconfig " + serviceName + " on")))
             .requiringExitCodeZero()
             .newTask();
 
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/AbstractMultiDistroLiveTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/AbstractMultiDistroLiveTest.java
index ff29408e36..38a6cb37b7 100644
--- a/software/base/src/test/java/org/apache/brooklyn/entity/AbstractMultiDistroLiveTest.java
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/AbstractMultiDistroLiveTest.java
@@ -34,7 +34,8 @@ import org.apache.brooklyn.location.jclouds.JcloudsLocationConfig;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.test.Asserts;
 import org.apache.brooklyn.util.collections.MutableMap;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.core.file.BrooklynOsCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.time.Duration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -142,7 +143,7 @@ public abstract class AbstractMultiDistroLiveTest extends BrooklynAppLiveTestSup
         Asserts.succeedsEventually(ImmutableMap.of("timeout", Duration.FIVE_MINUTES), new Runnable() {
             @Override
             public void run() {
-                assertExecSsh(server, ImmutableList.of(BashCommands.installPackage("curl"), "netstat -antp", "curl -k --retry 3 "+url));
+                assertExecSsh(server, ImmutableList.of(BrooklynOsCommands.bash(server).installPackage("curl"), "netstat -antp", "curl -k --retry 3 "+url));
             }});
     }
 }
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/mysql/DynamicToyMySqlEntityBuilder.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/mysql/DynamicToyMySqlEntityBuilder.java
index 5fda8bbbef..98ce92a8a9 100644
--- a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/mysql/DynamicToyMySqlEntityBuilder.java
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/mysql/DynamicToyMySqlEntityBuilder.java
@@ -18,8 +18,11 @@
  */
 package org.apache.brooklyn.entity.software.base.test.mysql;
 
-import java.io.File;
-
+import com.google.common.base.Predicates;
+import com.google.common.base.Splitter;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.common.collect.Iterables;
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.entity.EntityInitializer;
 import org.apache.brooklyn.api.entity.EntityLocal;
@@ -34,21 +37,17 @@ import org.apache.brooklyn.entity.stock.BasicStartable;
 import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation.LocalhostMachine;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.file.BrooklynOsCommands;
 import org.apache.brooklyn.util.core.task.DynamicTasks;
 import org.apache.brooklyn.util.core.task.Tasks;
 import org.apache.brooklyn.util.core.task.ssh.SshTasks;
 import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
-import org.apache.brooklyn.util.ssh.BashCommands;
 import org.apache.brooklyn.util.time.Duration;
 import org.apache.brooklyn.util.time.Time;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.base.Predicates;
-import com.google.common.base.Splitter;
-import com.google.common.base.Supplier;
-import com.google.common.base.Suppliers;
-import com.google.common.collect.Iterables;
+import java.io.File;
 
 public class DynamicToyMySqlEntityBuilder {
 
@@ -109,10 +108,10 @@ public class DynamicToyMySqlEntityBuilder {
                         SshEffectorTasks.ssh(
                             "mkdir "+dir(entity),
                             "cd "+dir(entity),
-                            BashCommands.downloadToStdout(downloadUrl(entity, isLocalhost(machineS)))+" | tar xvz"
+                            BrooklynOsCommands.bash(entity).downloadToStdout(downloadUrl(entity, isLocalhost(machineS)))+" | tar xvz"
                         ).summary("download mysql").returning(SshTasks.returningStdoutLoggingInfo(log, true)));
                 if (isLinux(machineS)) {
-                    DynamicTasks.queue(SshEffectorTasks.ssh(BashCommands.installPackage("libaio1")));
+                    DynamicTasks.queue(SshEffectorTasks.ssh(BrooklynOsCommands.bash(entity).installPackage("libaio1")));
                 }
                 DynamicTasks.queue(
                         SshEffectorTasks.put(".my.cnf")
@@ -128,7 +127,7 @@ public class DynamicToyMySqlEntityBuilder {
             protected void postStartCustom(ConfigBag parameters) {
                 // if it's still up after 5s assume we are good
                 Time.sleep(Duration.FIVE_SECONDS);
-                if (!DynamicTasks.queue(SshEffectorTasks.isPidFromFileRunning(dir(entity)+"/*/data/*.pid")).get()) {
+                if (!DynamicTasks.queue(SshEffectorTasks.isPidFromFileRunning(BrooklynOsCommands.bash(entity()), dir(entity)+"/*/data/*.pid")).get()) {
                     // but if it's not up add a bunch of other info
                     log.warn("MySQL did not start: "+dir(entity));
                     ProcessTaskWrapper<Integer> info = DynamicTasks.queue(SshEffectorTasks.ssh(
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/system_service/SystemServiceEnricherTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/system_service/SystemServiceEnricherTest.java
index 7cf1f3fa54..9a95ce0d4a 100644
--- a/software/base/src/test/java/org/apache/brooklyn/entity/system_service/SystemServiceEnricherTest.java
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/system_service/SystemServiceEnricherTest.java
@@ -31,13 +31,13 @@ import org.apache.brooklyn.core.test.BrooklynAppLiveTestSupport;
 import org.apache.brooklyn.entity.software.base.VanillaSoftwareProcess;
 import org.apache.brooklyn.entity.software.base.VanillaSoftwareProcessImpl;
 import org.apache.brooklyn.entity.software.base.VanillaSoftwareProcessSshDriver;
-import org.apache.brooklyn.entity.system_service.SystemServiceEnricher;
+import org.apache.brooklyn.util.ssh.BashCommands;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 import org.apache.brooklyn.location.jclouds.JcloudsLocation;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.test.Asserts;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.time.Duration;
 
 import com.google.common.base.Suppliers;
diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/ssh/IptablesCommandsFirewalldTest.java b/utils/common/src/test/java/org/apache/brooklyn/util/ssh/IptablesCommandsFirewalldTest.java
index 78d202b536..7056d3ae3e 100644
--- a/utils/common/src/test/java/org/apache/brooklyn/util/ssh/IptablesCommandsFirewalldTest.java
+++ b/utils/common/src/test/java/org/apache/brooklyn/util/ssh/IptablesCommandsFirewalldTest.java
@@ -19,9 +19,8 @@
 package org.apache.brooklyn.util.ssh;
 
 import org.apache.brooklyn.util.net.Protocol;
-import org.apache.brooklyn.util.ssh.IptablesCommands;
-import org.apache.brooklyn.util.ssh.IptablesCommands.Chain;
-import org.apache.brooklyn.util.ssh.IptablesCommands.Policy;
+import org.apache.brooklyn.util.ssh.IptablesCommandsConfigurable.Chain;
+import org.apache.brooklyn.util.ssh.IptablesCommandsConfigurable.Policy;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
@@ -66,39 +65,41 @@ public class IptablesCommandsFirewalldTest {
             + "else echo \"( { which systemctl && systemctl is-active firewalld ; } || /usr/bin/systemctl is-active firewalld )\" | "
             + "sudo -E -n -S -s -- bash ; fi )";
 
+    static IptablesCommandsConfigurable testInstance = new IptablesCommandsConfigurable(BashCommandsConfigurable.newInstance());
+
     @Test
     public void testAddFirewalldRule() {
-        Assert.assertEquals(IptablesCommands.addFirewalldRule(Chain.INPUT,
+        Assert.assertEquals(testInstance.addFirewalldRule(Chain.INPUT,
                 Protocol.TCP, 3306, Policy.ACCEPT), addFirewalldRule);
     }
 
     @Test
     public void testFirewalldService() {
-        Assert.assertEquals(IptablesCommands.firewalldService("status"), firewalldService);
+        Assert.assertEquals(testInstance.firewalldService("status"), firewalldService);
     }
 
     @Test
     public void testFirewalldServiceRestart() {
-        Assert.assertEquals(IptablesCommands.firewalldServiceRestart(), firewalldServiceRestart);
+        Assert.assertEquals(testInstance.firewalldServiceRestart(), firewalldServiceRestart);
     }
 
     @Test
     public void testFirewalldServiceStart() {
-        Assert.assertEquals(IptablesCommands.firewalldServiceStart(), firewalldServiceStart);
+        Assert.assertEquals(testInstance.firewalldServiceStart(), firewalldServiceStart);
     }
 
     @Test
     public void testFirewalldServiceStatus() {
-        Assert.assertEquals(IptablesCommands.firewalldServiceStatus(), firewalldServiceStatus);
+        Assert.assertEquals(testInstance.firewalldServiceStatus(), firewalldServiceStatus);
     }
 
     @Test
     public void testFirewalldServiceStop() {
-        Assert.assertEquals(IptablesCommands.firewalldServiceStop(), firewalldServiceStop);
+        Assert.assertEquals(testInstance.firewalldServiceStop(), firewalldServiceStop);
     }
 
     @Test
     public void testFirewalldServiceIsActive() {
-        Assert.assertEquals(IptablesCommands.firewalldServiceIsActive(), firewalldServiceIsActive);
+        Assert.assertEquals(testInstance.firewalldServiceIsActive(), firewalldServiceIsActive);
     }
 }
diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/ssh/IptablesCommandsTest.java b/utils/common/src/test/java/org/apache/brooklyn/util/ssh/IptablesCommandsTest.java
index 29d80be05e..be5843a76b 100644
--- a/utils/common/src/test/java/org/apache/brooklyn/util/ssh/IptablesCommandsTest.java
+++ b/utils/common/src/test/java/org/apache/brooklyn/util/ssh/IptablesCommandsTest.java
@@ -19,9 +19,8 @@
 package org.apache.brooklyn.util.ssh;
 
 import org.apache.brooklyn.util.net.Protocol;
-import org.apache.brooklyn.util.ssh.IptablesCommands;
-import org.apache.brooklyn.util.ssh.IptablesCommands.Chain;
-import org.apache.brooklyn.util.ssh.IptablesCommands.Policy;
+import org.apache.brooklyn.util.ssh.IptablesCommandsConfigurable.Chain;
+import org.apache.brooklyn.util.ssh.IptablesCommandsConfigurable.Policy;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
@@ -52,37 +51,40 @@ public class IptablesCommandsTest {
             "(( echo \"WARNING: no known/successful package manager to install iptables-persistent, may fail subsequently\" | tee /dev/stderr ) || true) ) " +
             "&& ( if test \"$UID\" -eq 0; then ( /etc/init.d/iptables-persistent save ); else sudo -E -n -S -- /etc/init.d/iptables-persistent save; fi ) ) )";
 
+    static IptablesCommandsConfigurable testInstance = new IptablesCommandsConfigurable(BashCommandsConfigurable.newInstance());
+
     @Test
     public void testCleanUpIptablesRules() {
-        Assert.assertEquals(IptablesCommands.cleanUpIptablesRules(), cleanUptptablesRules);
+        Assert.assertEquals(testInstance.cleanUpIptablesRules(), cleanUptptablesRules);
     }
 
     @Test
     public void testInsertIptablesRules() {
-        Assert.assertEquals(IptablesCommands.insertIptablesRule(Chain.INPUT, "eth0", Protocol.TCP, 3306, Policy.ACCEPT),
+        Assert.assertEquals(testInstance.insertIptablesRule(Chain.INPUT, "eth0", Protocol.TCP, 3306, Policy.ACCEPT),
                 insertIptablesRule);
     }
 
     @Test
     public void testAppendIptablesRules() {
-        Assert.assertEquals(IptablesCommands.appendIptablesRule(Chain.INPUT, "eth0", Protocol.TCP, 3306, Policy.ACCEPT),
+        Assert.assertEquals(testInstance.appendIptablesRule(Chain.INPUT, "eth0", Protocol.TCP, 3306, Policy.ACCEPT),
                 appendIptablesRule);
     }
 
     @Test
     public void testInsertIptablesRulesForAllInterfaces() {
-        Assert.assertEquals(IptablesCommands.insertIptablesRule(Chain.INPUT, Protocol.TCP, 3306, Policy.ACCEPT),
+        Assert.assertEquals(testInstance.insertIptablesRule(Chain.INPUT, Protocol.TCP, 3306, Policy.ACCEPT),
                 insertIptablesRuleAll);
     }
 
     @Test
     public void testAppendIptablesRulesForAllInterfaces() {
-        Assert.assertEquals(IptablesCommands.appendIptablesRule(Chain.INPUT, Protocol.TCP, 3306, Policy.ACCEPT),
+        Assert.assertEquals(testInstance.appendIptablesRule(Chain.INPUT, Protocol.TCP, 3306, Policy.ACCEPT),
                 appendIptablesRuleAll);
     }
 
     @Test
     public void testSaveIptablesRules() {
-        Assert.assertEquals(IptablesCommands.saveIptablesRules(), saveIptablesRules);
+        Assert.assertEquals(testInstance.saveIptablesRules(), saveIptablesRules);
     }
+
 }


[brooklyn-server] 02/06: update other places that ignore certs to use BashCommandsConfigurable settings detection

Posted by he...@apache.org.
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 11d1949f14b5e10570f90ceecf928a38fc2d841a
Author: Alex Heneveld <al...@cloudsoft.io>
AuthorDate: Fri Jul 29 02:08:49 2022 +0100

    update other places that ignore certs to use BashCommandsConfigurable settings detection
---
 .../apache/brooklyn/location/ssh/SshMachineLocation.java | 16 +++++++++-------
 1 file changed, 9 insertions(+), 7 deletions(-)

diff --git a/core/src/main/java/org/apache/brooklyn/location/ssh/SshMachineLocation.java b/core/src/main/java/org/apache/brooklyn/location/ssh/SshMachineLocation.java
index 666f2ba51b..99f81a9994 100644
--- a/core/src/main/java/org/apache/brooklyn/location/ssh/SshMachineLocation.java
+++ b/core/src/main/java/org/apache/brooklyn/location/ssh/SshMachineLocation.java
@@ -75,6 +75,7 @@ import org.apache.brooklyn.util.core.ResourceUtils;
 import org.apache.brooklyn.util.core.config.ConfigBag;
 import org.apache.brooklyn.util.core.crypto.SecureKeys;
 import org.apache.brooklyn.util.core.file.ArchiveUtils;
+import org.apache.brooklyn.util.core.file.BrooklynOsCommands;
 import org.apache.brooklyn.util.core.flags.SetFromFlag;
 import org.apache.brooklyn.util.core.flags.TypeCoercions;
 import org.apache.brooklyn.util.core.internal.ssh.ShellTool;
@@ -92,7 +93,7 @@ import org.apache.brooklyn.util.guava.KeyTransformingLoadingCache.KeyTransformin
 import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.pool.BasicPool;
 import org.apache.brooklyn.util.pool.Pool;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.stream.KnownSizeInputStream;
 import org.apache.brooklyn.util.stream.ReaderInputStream;
 import org.apache.brooklyn.util.stream.StreamGobbler;
@@ -869,15 +870,16 @@ public class SshMachineLocation extends AbstractMachineLocation implements Machi
         LOG.debug("installing {} to {} on {}, attempting remote curl", new Object[] { url, destPath, this });
 
         try {
+            BashCommandsConfigurable bash = BrooklynOsCommands.bash(getManagementContext());
             PipedInputStream insO = new PipedInputStream(); OutputStream outO = new PipedOutputStream(insO);
             PipedInputStream insE = new PipedInputStream(); OutputStream outE = new PipedOutputStream(insE);
             StreamGobbler sgsO = new StreamGobbler(insO, null, LOG); sgsO.setLogPrefix("[curl @ "+address+":stdout] ").start();
             StreamGobbler sgsE = new StreamGobbler(insE, null, LOG); sgsE.setLogPrefix("[curl @ "+address+":stderr] ").start();
             Map<String, ?> sshProps = MutableMap.<String, Object>builder().putAll(props).put("out", outO).put("err", outE).build();
             int result = execScript(sshProps, "copying remote resource "+url+" to server",  ImmutableList.of(
-                    BashCommands.INSTALL_CURL, // TODO should hold the 'installing' mutex
+                    bash.INSTALL_CURL, // TODO should hold the 'installing' mutex
                     "mkdir -p `dirname '"+destPath+"'`",
-                    "curl "+url+" -L --silent --insecure --show-error --fail --connect-timeout 60 --max-time 600 --retry 5 -o '"+destPath+"'"));
+                    "curl "+url+" -L --silent"+(bash.isIgnoreCerts() ? " --insecure" : "")+" --show-error --fail --connect-timeout 60 --max-time 600 --retry 5 -o '"+destPath+"'"));
             sgsO.close();
             sgsE.close();
             if (result != 0) {
@@ -1061,10 +1063,10 @@ public class SshMachineLocation extends AbstractMachineLocation implements Machi
     @Override
     public String resolveOnBoxDirFor(Entity entity, String unresolvedPath) {
         ProcessTaskWrapper<Integer> baseTask = SshEffectorTasks.ssh(
-            BashCommands.alternatives("mkdir -p \"${BASE_DIR}\"",
-                BashCommands.chain(
-                    BashCommands.sudo("mkdir -p \"${BASE_DIR}\""),
-                    BashCommands.sudo("chown "+getUser()+" \"${BASE_DIR}\""))),
+            BrooklynOsCommands.bash(entity).alternatives("mkdir -p \"${BASE_DIR}\"",
+                BrooklynOsCommands.bash(entity).chain(
+                    BrooklynOsCommands.bash(entity).sudo("mkdir -p \"${BASE_DIR}\""),
+                    BrooklynOsCommands.bash(entity).sudo("chown "+getUser()+" \"${BASE_DIR}\""))),
             "cd ~",
             "cd ${BASE_DIR}",
             "echo BASE_DIR_RESULT':'`pwd`:BASE_DIR_RESULT")


[brooklyn-server] 06/06: allow use of trust all to be configured via brooklyn properties and entity config

Posted by he...@apache.org.
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 21da50685935380be46a6819c0441f97b879a752
Author: Alex Heneveld <al...@cloudsoft.io>
AuthorDate: Fri Jul 29 14:26:13 2022 +0100

    allow use of trust all to be configured via brooklyn properties and entity config
    
    see BrooklynHttpConfig
---
 .../catalog/internal/BasicBrooklynCatalog.java     |   2 +-
 .../PropertiesFileExternalConfigSupplier.java      |   4 +-
 .../vault/VaultExternalConfigSupplier.java         |   3 +-
 .../core/effector/http/HttpCommandEffector.java    |  54 +++----
 .../core/location/LocationConfigUtils.java         |  35 +++--
 .../mgmt/ha/BrooklynBomOsgiArchiveInstaller.java   |   2 +-
 .../org/apache/brooklyn/feed/http/HttpFeed.java    |  60 +++-----
 .../brooklyn/location/ssh/SshMachineLocation.java  |   2 +-
 .../apache/brooklyn/util/core/ResourceUtils.java   |  38 ++++-
 .../util/core/javalang/BrooklynHttpConfig.java     | 155 +++++++++++++++++++++
 .../brooklyn/util/core/osgi/BundleMaker.java       |   2 +-
 .../util/core/task/ssh/SshPutTaskFactory.java      |   7 +
 .../util/executor/HttpExecutorFactoryImpl.java     |  10 +-
 .../brooklyn/util/core/ResourceUtilsTest.java      |   2 +-
 .../location/jclouds/CreateUserStatements.java     |   3 +-
 .../brooklyn/location/jclouds/JcloudsLocation.java |   6 +-
 .../jclouds/JcloudsSshMachineLocation.java         |   3 +-
 .../entity/brooklynnode/DeployBlueprintTest.java   |   4 +-
 .../brooklyn/rest/BrooklynRestApiLauncherTest.java |   2 +-
 .../entity/brooklynnode/EntityHttpClientImpl.java  |   6 +-
 .../brooklynnode/BrooklynNodeIntegrationTest.java  |  23 ++-
 .../org/apache/brooklyn/test/HttpTestUtils.java    | 138 +++++-------------
 .../org/apache/brooklyn/test/WebAppMonitor.java    |   3 +-
 .../org/apache/brooklyn/util/http/HttpAsserts.java |  16 +--
 .../org/apache/brooklyn/util/http/HttpTool.java    |  50 ++++---
 .../util/http/TrustingSslSocketFactory.java        |   6 +-
 .../executor/apacheclient/HttpExecutorImpl.java    |   7 +
 .../brooklyn/util/jmx/jmxmp/JmxmpClient.java       |  11 +-
 28 files changed, 394 insertions(+), 260 deletions(-)

diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java
index 8e742eb6cf..b8d37a3257 100644
--- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java
+++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java
@@ -1233,7 +1233,7 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
         // org.reflections requires the URL to be "file:" containg ".jar"
         File fJar = Os.newTempFile(containingBundle.getVersionedName().toOsgiString(), ".jar");
         try {
-            Streams.copy(ResourceUtils.create().getResourceFromUrl(url), new FileOutputStream(fJar));
+            Streams.copy(ResourceUtils.create(mgmt).getResourceFromUrl(url), new FileOutputStream(fJar));
             subCatalog.addToClasspath(new String[] { "file:"+fJar.getAbsolutePath() });
             Collection<CatalogItemDtoAbstract<?, ?>> result = scanAnnotationsInternal(mgmt, subCatalog, MutableMap.of("version", containingBundle.getSuppliedVersionString()), containingBundle);
             return result;
diff --git a/core/src/main/java/org/apache/brooklyn/core/config/external/PropertiesFileExternalConfigSupplier.java b/core/src/main/java/org/apache/brooklyn/core/config/external/PropertiesFileExternalConfigSupplier.java
index e9db17ea5b..eb640bcf34 100644
--- a/core/src/main/java/org/apache/brooklyn/core/config/external/PropertiesFileExternalConfigSupplier.java
+++ b/core/src/main/java/org/apache/brooklyn/core/config/external/PropertiesFileExternalConfigSupplier.java
@@ -54,10 +54,10 @@ public class PropertiesFileExternalConfigSupplier extends AbstractExternalConfig
         return properties.getProperty(key);
     }
 
-    private static Properties loadProperties(String propertiesUrl) throws IOException {
+    private Properties loadProperties(String propertiesUrl) throws IOException {
         InputStream is = null;
         try {
-            is = ResourceUtils.create().getResourceFromUrl(propertiesUrl);
+            is = ResourceUtils.create(getManagementContext()).getResourceFromUrl(propertiesUrl);
             Properties p = new Properties();
             p.load(is);
             return p;
diff --git a/core/src/main/java/org/apache/brooklyn/core/config/external/vault/VaultExternalConfigSupplier.java b/core/src/main/java/org/apache/brooklyn/core/config/external/vault/VaultExternalConfigSupplier.java
index 2a976f10ed..28f9c84362 100644
--- a/core/src/main/java/org/apache/brooklyn/core/config/external/vault/VaultExternalConfigSupplier.java
+++ b/core/src/main/java/org/apache/brooklyn/core/config/external/vault/VaultExternalConfigSupplier.java
@@ -29,6 +29,7 @@ import com.google.gson.JsonObject;
 import org.apache.brooklyn.api.mgmt.ManagementContext;
 import org.apache.brooklyn.core.config.external.AbstractExternalConfigSupplier;
 import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.javalang.BrooklynHttpConfig;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.http.HttpTool;
 import org.apache.brooklyn.util.http.HttpToolResponse;
@@ -69,7 +70,7 @@ public abstract class VaultExternalConfigSupplier extends AbstractExternalConfig
         super(managementContext, name);
         this.config = config;
         this.name = name;
-        httpClient = HttpTool.httpClientBuilder().build();
+        httpClient = BrooklynHttpConfig.httpClientBuilder(managementContext, true).build();
         gson = new GsonBuilder().create();
 
         List<String> errors = Lists.newArrayListWithCapacity(2);
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/http/HttpCommandEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/http/HttpCommandEffector.java
index 6a3fddfe11..ea21e2ca5f 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/http/HttpCommandEffector.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/http/HttpCommandEffector.java
@@ -18,21 +18,14 @@
  */
 package org.apache.brooklyn.core.effector.http;
 
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.net.MalformedURLException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
-import java.util.Map;
-import java.util.concurrent.Callable;
-
+import com.google.common.annotations.Beta;
+import com.google.common.base.Enums;
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+import com.google.common.base.Throwables;
+import com.google.common.io.ByteStreams;
+import com.google.common.net.HttpHeaders;
+import com.jayway.jsonpath.JsonPath;
 import org.apache.brooklyn.api.effector.Effector;
 import org.apache.brooklyn.api.mgmt.Task;
 import org.apache.brooklyn.config.ConfigKey;
@@ -45,26 +38,27 @@ import org.apache.brooklyn.core.entity.EntityInitializers;
 import org.apache.brooklyn.core.sensor.Sensors;
 import org.apache.brooklyn.util.collections.Jsonya;
 import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.javalang.BrooklynHttpConfig;
 import org.apache.brooklyn.util.core.task.Tasks;
 import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.apache.brooklyn.util.http.executor.HttpConfig;
+import org.apache.brooklyn.util.http.auth.UsernamePassword;
 import org.apache.brooklyn.util.http.executor.HttpExecutor;
 import org.apache.brooklyn.util.http.executor.HttpRequest;
 import org.apache.brooklyn.util.http.executor.HttpResponse;
-import org.apache.brooklyn.util.http.auth.UsernamePassword;
-import org.apache.brooklyn.util.http.executor.apacheclient.HttpExecutorImpl;
 import org.apache.brooklyn.util.text.Strings;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.annotations.Beta;
-import com.google.common.base.Enums;
-import com.google.common.base.Joiner;
-import com.google.common.base.Optional;
-import com.google.common.base.Throwables;
-import com.google.common.io.ByteStreams;
-import com.google.common.net.HttpHeaders;
-import com.jayway.jsonpath.JsonPath;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.*;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
  * An {@link Effector} to invoke REST endpoints.
@@ -141,7 +135,7 @@ public final class HttpCommandEffector extends AddEffectorInitializerAbstract {
             if(!Strings.isEmpty(jsonPath) && !pathsAndSensors.isEmpty()) {
                 throw new IllegalArgumentException("Both jsonPath and pathsAndSensors are defined, please pick just one to resolve the ambiguity");
             }
-            final HttpExecutor httpExecutor = HttpExecutorImpl.newInstance();
+            final HttpExecutor httpExecutor = BrooklynHttpConfig.newHttpExecutor(entity());
 
             final HttpRequest request = buildHttpRequest(httpVerb, uri, headers, httpUsername, httpPassword, payload);
             Task t = Tasks.builder().displayName(effector.getName()).body(new Callable<Object>() {
@@ -205,11 +199,7 @@ public final class HttpCommandEffector extends AddEffectorInitializerAbstract {
             HttpRequest.Builder httpRequestBuilder = new HttpRequest.Builder()
                     .uri(uri)
                     .method(httpVerb)
-                    .config(HttpConfig.builder()
-                            .trustSelfSigned(true)
-                            .trustAll(true)
-                            .laxRedirect(true)
-                            .build());
+                    .config(BrooklynHttpConfig.httpConfigBuilder(entity()).build());
 
             if (headers != null) {
                 httpRequestBuilder.headers(headers);
diff --git a/core/src/main/java/org/apache/brooklyn/core/location/LocationConfigUtils.java b/core/src/main/java/org/apache/brooklyn/core/location/LocationConfigUtils.java
index 73607af8cc..3011d8baf0 100644
--- a/core/src/main/java/org/apache/brooklyn/core/location/LocationConfigUtils.java
+++ b/core/src/main/java/org/apache/brooklyn/core/location/LocationConfigUtils.java
@@ -63,12 +63,18 @@ public class LocationConfigUtils {
      * {@link LocationConfigKeys#PRIVATE_KEY_DATA} and {@link LocationConfigKeys#PRIVATE_KEY_FILE}
      * (defaulting to the private key file + ".pub"). 
      **/
+    public static OsCredential getOsCredential(ConfigBag config, ResourceUtils ru) {
+        return OsCredential.newInstance(config).withResourceUtils(ru);
+    }
+
+    @Deprecated /** @deprecated since 1.1, pass a ResourceUtils for external lookup */
+    // only used in tests
     public static OsCredential getOsCredential(ConfigBag config) {
         return OsCredential.newInstance(config);
     }
     
     /** Convenience class for holding private/public keys and passwords, inferring from config keys.
-     * See {@link LocationConfigUtils#getOsCredential(ConfigBag)}. */
+     * See {@link LocationConfigUtils#getOsCredential(ConfigBag, ResourceUtils)}. */
     @Beta // would be nice to replace with a builder pattern 
     public static class OsCredential {
         private final ConfigBag config;
@@ -84,11 +90,17 @@ public class LocationConfigUtils {
         private String privateKeyData;
         private String publicKeyData;
         private String password;
+        private ResourceUtils resourceUtils;
         
         private OsCredential(ConfigBag config) {
             this.config = config;
         }
 
+        public OsCredential withResourceUtils(ResourceUtils ru) {
+            this.resourceUtils = ru;
+            return this;
+        }
+
         /** throws if there are any problems */
         public OsCredential checkNotEmpty() {
             checkNoErrors();
@@ -200,7 +212,12 @@ public class LocationConfigUtils {
         public static OsCredential newInstance(ConfigBag config) {
             return new OsCredential(config);
         }
-        
+
+        private ResourceUtils getResourceUtils() {
+            if (resourceUtils==null) resourceUtils = ResourceUtils.create("OsCredentials-uninitialized");  //shouldn't be used
+            return resourceUtils;
+        }
+
         private synchronized void infer() {
             if (!dirty) return;
             warningMessages.clear(); 
@@ -208,7 +225,7 @@ public class LocationConfigUtils {
             log.debug("Inferring OS credentials");
             privateKeyData = config.get(LocationConfigKeys.PRIVATE_KEY_DATA);
             password = config.get(LocationConfigKeys.PASSWORD);
-            publicKeyData = getKeyDataFromDataKeyOrFileKey(config, LocationConfigKeys.PUBLIC_KEY_DATA, LocationConfigKeys.PUBLIC_KEY_FILE);
+            publicKeyData = getKeyDataFromDataKeyOrFileKey(config, LocationConfigKeys.PUBLIC_KEY_DATA, LocationConfigKeys.PUBLIC_KEY_FILE, getResourceUtils());
 
             KeyPair privateKey = null;
             
@@ -228,7 +245,7 @@ public class LocationConfigUtils {
                                 // not real important, but we get it for free if "files" is a list instead.
                                 // using ResourceUtils is useful for classpath resources
                                 if (file!=null)
-                                    privateKeyData = ResourceUtils.create().getResourceAsString(file);
+                                    privateKeyData = getResourceUtils().getResourceAsString(file);
                                 // else use data already set
                                 
                                 privateKey = getValidatedPrivateKey(file);
@@ -241,7 +258,7 @@ public class LocationConfigUtils {
                                 } else {
                                     String publicKeyFile = (file!=null ? file+".pub" : "(data)");
                                     try {
-                                        publicKeyData = ResourceUtils.create().getResourceAsString(publicKeyFile);
+                                        publicKeyData = getResourceUtils().getResourceAsString(publicKeyFile);
                                         
                                         log.debug("Loaded private key data from "+file+
                                             " and public key data from "+publicKeyFile);
@@ -363,7 +380,7 @@ public class LocationConfigUtils {
         }
     }
 
-    private static String getKeyDataFromDataKeyOrFileKey(ConfigBag config, ConfigKey<String> dataKey, ConfigKey<String> fileKey) {
+    private static String getKeyDataFromDataKeyOrFileKey(ConfigBag config, ConfigKey<String> dataKey, ConfigKey<String> fileKey, ResourceUtils ru) {
         boolean unused = config.isUnused(dataKey);
         String data = config.get(dataKey);
         if (Strings.isNonBlank(data) && !unused) {
@@ -374,7 +391,7 @@ public class LocationConfigUtils {
         if (groovyTruth(file)) {
             List<String> files = Arrays.asList(file.split(File.pathSeparator));
             List<String> filesTidied = tidyFilePaths(files);
-            String fileData = getFileContents(filesTidied);
+            String fileData = getFileContents(filesTidied, ru);
             if (fileData == null) {
                 log.warn("Invalid file" + (files.size() > 1 ? "s" : "") + " for " + fileKey + " (given " + files + 
                         (files.equals(filesTidied) ? "" : "; converted to " + filesTidied) + ") " +
@@ -398,14 +415,14 @@ public class LocationConfigUtils {
      *  
      * @param files             list of file paths
      */
-    private static String getFileContents(Iterable<String> files) {
+    private static String getFileContents(Iterable<String> files, ResourceUtils ru) {
         Iterator<String> fi = files.iterator();
         while (fi.hasNext()) {
             String file = fi.next();
             if (groovyTruth(file)) {
                 try {
                     // see comment above
-                    String result = ResourceUtils.create().getResourceAsString(file);
+                    String result = ru.getResourceAsString(file);
                     if (result!=null) return result;
                     log.debug("Invalid file "+file+" ; " + (!fi.hasNext() ? "no more files to try" : "trying next file")+" (null)");
                 } catch (Exception e) {
diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/BrooklynBomOsgiArchiveInstaller.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/BrooklynBomOsgiArchiveInstaller.java
index fc1440738d..bccc8d0d50 100644
--- a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/BrooklynBomOsgiArchiveInstaller.java
+++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/BrooklynBomOsgiArchiveInstaller.java
@@ -330,7 +330,7 @@ public class BrooklynBomOsgiArchiveInstaller {
                             // get a handle on the zip file (although we could skip if not doing persistence - but that feels even worse than this!)
                             try {
                                 url = Strings.removeFromStart(url, "system:");
-                                File zipTemp = new BundleMaker(ResourceUtils.create()).createJarFromClasspathDir(url);
+                                File zipTemp = new BundleMaker(ResourceUtils.create(mgmt)).createJarFromClasspathDir(url);
                                 zipIn = new FileInputStream(zipTemp);
                             } catch (FileNotFoundException e) {
                                 throw Exceptions.propagate(e);
diff --git a/core/src/main/java/org/apache/brooklyn/feed/http/HttpFeed.java b/core/src/main/java/org/apache/brooklyn/feed/http/HttpFeed.java
index c1897173a2..2f0247bc8b 100644
--- a/core/src/main/java/org/apache/brooklyn/feed/http/HttpFeed.java
+++ b/core/src/main/java/org/apache/brooklyn/feed/http/HttpFeed.java
@@ -18,20 +18,11 @@
  */
 package org.apache.brooklyn.feed.http;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.net.URI;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.Callable;
-import java.util.concurrent.TimeUnit;
-
+import com.google.common.base.*;
+import com.google.common.collect.*;
+import com.google.common.io.BaseEncoding;
+import com.google.common.io.ByteStreams;
+import com.google.common.reflect.TypeToken;
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.entity.EntityLocal;
 import org.apache.brooklyn.api.location.Location;
@@ -46,15 +37,14 @@ import org.apache.brooklyn.core.feed.Poller;
 import org.apache.brooklyn.core.location.Locations;
 import org.apache.brooklyn.core.location.Machines;
 import org.apache.brooklyn.core.location.internal.LocationInternal;
+import org.apache.brooklyn.util.core.javalang.BrooklynHttpConfig;
 import org.apache.brooklyn.util.executor.HttpExecutorFactory;
 import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.http.HttpToolResponse;
-import org.apache.brooklyn.util.http.executor.HttpConfig;
+import org.apache.brooklyn.util.http.auth.UsernamePassword;
 import org.apache.brooklyn.util.http.executor.HttpExecutor;
 import org.apache.brooklyn.util.http.executor.HttpRequest;
 import org.apache.brooklyn.util.http.executor.HttpResponse;
-import org.apache.brooklyn.util.http.auth.UsernamePassword;
-import org.apache.brooklyn.util.http.executor.apacheclient.HttpExecutorImpl;
 import org.apache.brooklyn.util.stream.Streams;
 import org.apache.brooklyn.util.time.Duration;
 import org.apache.http.auth.Credentials;
@@ -62,20 +52,19 @@ import org.apache.http.auth.UsernamePasswordCredentials;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.base.Objects;
-import com.google.common.base.Optional;
-import com.google.common.base.Supplier;
-import com.google.common.base.Suppliers;
-import com.google.common.base.Throwables;
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.SetMultimap;
-import com.google.common.collect.Sets;
-import com.google.common.io.BaseEncoding;
-import com.google.common.io.ByteStreams;
-import com.google.common.reflect.TypeToken;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+
+import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
  * Provides a feed of attribute values, by polling over http.
@@ -346,7 +335,8 @@ public class HttpFeed extends AbstractFeed {
                 Map<String, Object> httpExecutorProps = ((LocationInternal)location.get()).config().getBag().getAllConfig();
                 httpExecutor = httpExecutorFactory.getHttpExecutor(httpExecutorProps);
             } else {
-                httpExecutor = HttpExecutorImpl.newInstance();
+                httpExecutor = BrooklynHttpConfig.newHttpExecutor(
+                        Preconditions.checkNotNull(location.isPresent() ? location.get() : builder.entity, "feed requires a location or entity") );
             }
         }
 
@@ -423,11 +413,7 @@ public class HttpFeed extends AbstractFeed {
                             .credentials(creds)
                             .method(pollInfo.method)
                             .body(pollInfo.body)
-                            .config(HttpConfig.builder()
-                                    .trustSelfSigned(true)
-                                    .trustAll(true)
-                                    .laxRedirect(true)
-                                    .build())
+                            .config(BrooklynHttpConfig.httpConfigBuilder(getEntity()).build())
                             .build());
                     return createHttpToolRespose(response, startTime);
                 }};
diff --git a/core/src/main/java/org/apache/brooklyn/location/ssh/SshMachineLocation.java b/core/src/main/java/org/apache/brooklyn/location/ssh/SshMachineLocation.java
index 9f7a8e3d1d..a1619b7e00 100644
--- a/core/src/main/java/org/apache/brooklyn/location/ssh/SshMachineLocation.java
+++ b/core/src/main/java/org/apache/brooklyn/location/ssh/SshMachineLocation.java
@@ -1018,7 +1018,7 @@ public class SshMachineLocation extends AbstractMachineLocation implements Machi
 
     /** returns the un-passphrased key-pair info if a key is being used, or else null */
     public KeyPair findKeyPair() {
-        OsCredential creds = LocationConfigUtils.getOsCredential(config().getBag());
+        OsCredential creds = LocationConfigUtils.getOsCredential(config().getBag(), ResourceUtils.create(this));
         if (creds.hasKey()) {
             String data = creds.getPrivateKeyData();
             return SecureKeys.readPem(data.getBytes(), getConfig(SshTool.PROP_PRIVATE_KEY_PASSPHRASE));
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/ResourceUtils.java b/core/src/main/java/org/apache/brooklyn/util/core/ResourceUtils.java
index b5bb384440..1191b05125 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/ResourceUtils.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/ResourceUtils.java
@@ -32,20 +32,26 @@ import java.net.URL;
 import java.net.URLDecoder;
 import java.util.List;
 import java.util.NoSuchElementException;
+import java.util.function.Supplier;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import javax.annotation.Nullable;
 
+import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.mgmt.ManagementContext;
 import org.apache.brooklyn.api.mgmt.classloading.BrooklynClassLoadingContext;
+import org.apache.brooklyn.api.objs.BrooklynObject;
 import org.apache.brooklyn.api.typereg.RegisteredType;
 import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog.BrooklynLoaderTracker;
 import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
 import org.apache.brooklyn.core.internal.BrooklynInitialization;
+import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
 import org.apache.brooklyn.core.mgmt.classloading.JavaBrooklynClassLoadingContext;
 import org.apache.brooklyn.core.mgmt.classloading.OsgiBrooklynClassLoadingContext;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.javalang.BrooklynHttpConfig;
+import org.apache.brooklyn.util.core.task.Tasks;
 import org.apache.brooklyn.util.core.text.DataUriSchemeParser;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.http.HttpTool;
@@ -139,9 +145,9 @@ public class ResourceUtils {
 
     public static final ResourceUtils create(RegisteredType type, ManagementContext mgmt, boolean includeThreadAndJavaClassLoader) {
         if (includeThreadAndJavaClassLoader) {
-            return create(CatalogUtils.newClassLoadingContext(mgmt, type), type.getId());
+            return create(CatalogUtils.newClassLoadingContext(mgmt, type), type.getId()).withHttpClientBuilder(() -> BrooklynHttpConfig.httpClientBuilder(mgmt, true));
         } else {
-            return create(new OsgiBrooklynClassLoadingContext(mgmt, type.getId(), type.getLibraries()), type.getId());
+            return create(new OsgiBrooklynClassLoadingContext(mgmt, type.getId(), type.getLibraries()), type.getId()).withHttpClientBuilder(() -> BrooklynHttpConfig.httpClientBuilder(mgmt, true));
         }
     }
 
@@ -149,6 +155,7 @@ public class ResourceUtils {
      * Creates a {@link ResourceUtils} object with itself as the context.
      *
      * @see ResourceUtils#create(Object)
+     * @deprecated since 1.1, supply an object such as an entity or adjunct to use the correct classpaths and correct certificate trust settings
      */
     public static final ResourceUtils create() {
         return new ResourceUtils(null);
@@ -165,7 +172,7 @@ public class ResourceUtils {
     }
 
     public ResourceUtils(Object contextObject, String contextMessage) {
-        this(contextObject==null ? null : getClassLoadingContextInternal(null, contextObject), contextObject, contextMessage);
+        this(contextObject==null || contextObject instanceof String ? null : getClassLoadingContextInternal(null, contextObject), contextObject, contextMessage);
     }
 
     public ResourceUtils(Object contextObject) {
@@ -203,7 +210,7 @@ public class ResourceUtils {
      * Better for callers use {@link CatalogUtils#getClassLoadingContext(org.apache.brooklyn.api.entity.Entity)} or similar. }.
      */
     private BrooklynClassLoadingContext getLoader() {
-        return (loader!=null ? loader : getClassLoadingContextInternal(null, contextObject!=null ? contextObject : this));
+        return (loader!=null ? loader : getClassLoadingContextInternal(null, contextObject!=null && !(contextObject instanceof String) ? contextObject : this));
     }
 
     /**
@@ -430,11 +437,28 @@ public class ResourceUtils {
         }
     }
 
+    Supplier<HttpClientBuilder> httpClientBuilderSupplier;
+
+    public ResourceUtils withHttpClientBuilder(Supplier<HttpClientBuilder> httpClientBuilderSupplier) {
+        this.httpClientBuilderSupplier = httpClientBuilderSupplier;
+        return this;
+    }
+
+    protected HttpClientBuilder newHttpClientBuilder() {
+        if (httpClientBuilderSupplier!=null) return httpClientBuilderSupplier.get();
+
+        if (contextObject instanceof ManagementContext) return BrooklynHttpConfig.httpClientBuilder( (ManagementContext) contextObject, true );
+        else if (contextObject instanceof BrooklynObject) return BrooklynHttpConfig.httpClientBuilder((BrooklynObject) contextObject);
+
+        Entity entity = BrooklynTaskTags.getContextEntity(Tasks.current());
+        if (entity!=null) return BrooklynHttpConfig.httpClientBuilder(entity);
+
+        return BrooklynHttpConfig.httpClientBuilderDefaultStrict();
+    }
+
     private InputStream getResourceViaHttp(String resource, @Nullable String username, @Nullable String password) throws IOException {
         URI uri = URI.create(resource);
-        HttpClientBuilder builder = HttpTool.httpClientBuilder()
-                .laxRedirect(true)
-                .uri(uri);
+        HttpClientBuilder builder = newHttpClientBuilder().uri(uri);
         Credentials credentials;
         if (username != null) {
             credentials = new UsernamePasswordCredentials(username, password);
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/javalang/BrooklynHttpConfig.java b/core/src/main/java/org/apache/brooklyn/util/core/javalang/BrooklynHttpConfig.java
new file mode 100644
index 0000000000..db470eb5e9
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/util/core/javalang/BrooklynHttpConfig.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.util.core.javalang;
+
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.api.objs.BrooklynObject;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
+import org.apache.brooklyn.core.objs.BrooklynObjectInternal;
+import org.apache.brooklyn.util.core.task.Tasks;
+import org.apache.brooklyn.util.http.HttpTool;
+import org.apache.brooklyn.util.http.executor.HttpConfig;
+import org.apache.brooklyn.util.http.executor.HttpExecutor;
+import org.apache.brooklyn.util.http.executor.apacheclient.HttpExecutorImpl;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class BrooklynHttpConfig {
+
+    public static final String HTTPS_CONFIG = "brooklyn.https.config.";
+    public static final ConfigKey<Boolean> TRUST_ALL = ConfigKeys.newBooleanConfigKey(HTTPS_CONFIG + "trustAll",
+        "Whether HTTPS and TLS connections should trust all certificates");
+    public static final ConfigKey<Boolean> TRUST_SELF_SIGNED = ConfigKeys.newBooleanConfigKey(HTTPS_CONFIG + "trustSelfSigned",
+            "Whether HTTPS and TLS connections should trust self-signed certificates");
+    public static final ConfigKey<Boolean> LAX_REDIRECT = ConfigKeys.newBooleanConfigKey(HTTPS_CONFIG + "laxRedirect",
+            "Whether HTTPS and TLS connections should be lax about redirecting");
+
+    private static final boolean DEFAULT_FOR_MGMT_LAX_AND_TRUSTING = true;
+
+    public static HttpConfig.Builder httpConfigBuilder(ManagementContext mgmt, boolean lookForContextEntity) {
+        return httpConfigBuilder(mgmt, DEFAULT_FOR_MGMT_LAX_AND_TRUSTING, lookForContextEntity);
+    }
+    public static HttpConfig.Builder httpConfigBuilder(ManagementContext mgmt, boolean defaultLaxAndTrusting, boolean lookForContextEntity) {
+        HttpConfig.Builder hcb = httpConfigBuilderDefault(defaultLaxAndTrusting, false);
+        apply(hcb, mgmt.getConfig()::getConfig);
+        if (lookForContextEntity) applyContextEntity(hcb);
+        return hcb;
+    }
+
+    private static void apply(HttpConfig.Builder hcb, Function<ConfigKey<Boolean>,Boolean> getter) {
+        applyIfNonNull(getter.apply(TRUST_ALL), hcb::trustAll);
+        applyIfNonNull(getter.apply(TRUST_SELF_SIGNED), hcb::trustSelfSigned);
+        applyIfNonNull(getter.apply(LAX_REDIRECT), hcb::laxRedirect);
+    }
+
+    private static void applyContextEntity(HttpConfig.Builder hcb) {
+        BrooklynObject entity = BrooklynTaskTags.getContextEntity(Tasks.current());
+        if (entity!=null) apply(hcb, entity.config()::get);
+    }
+
+    private static void applyIfNonNull(Boolean v, Consumer<Boolean> target) {
+        if (v!=null) target.accept(v);
+    }
+
+    public static HttpConfig.Builder httpConfigBuilder(BrooklynObject entity) {
+        return httpConfigBuilder(entity, DEFAULT_FOR_MGMT_LAX_AND_TRUSTING);
+    }
+    public static HttpConfig.Builder httpConfigBuilder(BrooklynObject entity, boolean defaultLaxAndTrusting) {
+        HttpConfig.Builder hcb = httpConfigBuilder(((BrooklynObjectInternal)entity).getManagementContext(), defaultLaxAndTrusting, false);
+        apply(hcb, entity.config()::get);
+        return hcb;
+    }
+
+    private static HttpConfig.Builder httpConfigBuilderDefault(boolean laxAndTrusting, boolean lookForContextEntities) {
+        HttpConfig.Builder hcb = HttpConfig.builder().laxRedirect(laxAndTrusting).trustAll(laxAndTrusting).trustSelfSigned(laxAndTrusting);
+        if (lookForContextEntities) applyContextEntity(hcb);
+        return hcb;
+    }
+
+
+
+    public static HttpTool.HttpClientBuilder httpClientBuilder(ManagementContext mgmt, boolean lookForContextEntity) {
+        return httpClientBuilder(mgmt, DEFAULT_FOR_MGMT_LAX_AND_TRUSTING, lookForContextEntity);
+    }
+    public static HttpTool.HttpClientBuilder httpClientBuilder(ManagementContext mgmt, boolean defaultLaxAndTrusting, boolean lookForContextEntity) {
+        HttpTool.HttpClientBuilder hcb = httpClientBuilderDefault(defaultLaxAndTrusting, false);
+        apply(hcb, mgmt.getConfig()::getConfig);
+        if (lookForContextEntity) {
+            applyContextEntity(hcb);
+        }
+        return hcb;
+    }
+
+    private static void apply(HttpTool.HttpClientBuilder hcb, Function<ConfigKey<Boolean>,Boolean> getter) {
+        applyIfNonNull(getter.apply(TRUST_ALL), hcb::trustAll);
+        applyIfNonNull(getter.apply(TRUST_SELF_SIGNED), hcb::trustSelfSigned);
+        applyIfNonNull(getter.apply(LAX_REDIRECT), hcb::laxRedirect);
+    }
+
+    private static void applyContextEntity(HttpTool.HttpClientBuilder hcb) {
+        BrooklynObject entity = BrooklynTaskTags.getContextEntity(Tasks.current());
+        if (entity!=null) apply(hcb, entity.config()::get);
+    }
+
+    public static HttpTool.HttpClientBuilder httpClientBuilderDefaultStrict() {
+        return httpClientBuilderDefault(false, false);
+    }
+
+    private static HttpTool.HttpClientBuilder httpClientBuilderDefault(boolean defaultLaxAndTrusting, boolean lookForContextEntity) {
+        HttpTool.HttpClientBuilder hcb = HttpTool.httpClientBuilder();
+        if (defaultLaxAndTrusting) {
+            hcb.trustAll(true);
+            hcb.trustSelfSigned(true);
+            hcb.laxRedirect(true);
+        }
+        if (lookForContextEntity) {
+            applyContextEntity(hcb);
+        }
+        return hcb;
+    }
+
+    public static HttpTool.HttpClientBuilder httpClientBuilder(BrooklynObject entity) {
+        return httpClientBuilder(entity, DEFAULT_FOR_MGMT_LAX_AND_TRUSTING);
+    }
+
+    public static HttpTool.HttpClientBuilder httpClientBuilder(BrooklynObject entity, boolean defaultLaxAndTrusting) {
+        HttpTool.HttpClientBuilder hcb = httpClientBuilder(((BrooklynObjectInternal)entity).getManagementContext(), defaultLaxAndTrusting, false);
+        apply(hcb, entity.config()::get);
+        return hcb;
+    }
+
+    public static HttpExecutor newHttpExecutor(BrooklynObject entity) {
+        return HttpExecutorImpl.newInstance().withConfig(httpConfigBuilder(entity, false).build());
+    }
+
+    public static HttpExecutor newHttpExecutorDefault() {
+        return HttpExecutorImpl.newInstance().withConfig(httpConfigBuilderDefault(false, true).build());
+    }
+
+    // SslTrustUtils.trustAll and TRUST_ALL -- only used in unsafe methods
+    // TrustingSslSocketFactory - only used in unsafe methods
+    // HttpTool and HttpTestUtils methods -- only used in tests and check code, methods marked unsafe, not for content
+    // HttpTool.TrustAllStrategy -- only used in unsafe methods above and by HttpClientBuilder which is routed above in production code
+    // HttpExecutorImpl -- only used with config supplied by above
+    // HttpExecutorFactory not set, except in tests; our HttpExecutorFactoryImpl only used in tests
+    // HttpConfig.Builder -- all uses routed through here
+}
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/osgi/BundleMaker.java b/core/src/main/java/org/apache/brooklyn/util/core/osgi/BundleMaker.java
index f2eeb0fb5c..1510e6d810 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/osgi/BundleMaker.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/osgi/BundleMaker.java
@@ -75,7 +75,7 @@ public class BundleMaker {
     }
     
     public BundleMaker(@Nonnull ManagementContext mgmt) {
-        this(((ManagementContextInternal) mgmt).getOsgiManager().get().getFramework(), ResourceUtils.create());
+        this(((ManagementContextInternal) mgmt).getOsgiManager().get().getFramework(), ResourceUtils.create(mgmt));
     }
 
     /** if set, this will be used to resolve relative classpath fragments;
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/SshPutTaskFactory.java b/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/SshPutTaskFactory.java
index 5e369d112c..5d65019ce8 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/SshPutTaskFactory.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/SshPutTaskFactory.java
@@ -21,6 +21,7 @@ package org.apache.brooklyn.util.core.task.ssh;
 import java.io.InputStream;
 import java.io.Reader;
 
+import com.google.common.base.Supplier;
 import org.apache.brooklyn.api.mgmt.TaskFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -89,6 +90,12 @@ public class SshPutTaskFactory extends SshPutTaskStub implements TaskFactory<Ssh
         return self();
     }
 
+    public SshPutTaskFactory contents(Supplier<InputStream> stream) {
+        markDirty();
+        this.contents = stream;
+        return self();
+    }
+
     public SshPutTaskFactory contents(Reader reader) {
         markDirty();
         this.contents = Suppliers.ofInstance(new ReaderInputStream(reader));  
diff --git a/core/src/main/java/org/apache/brooklyn/util/executor/HttpExecutorFactoryImpl.java b/core/src/main/java/org/apache/brooklyn/util/executor/HttpExecutorFactoryImpl.java
index b652dba0b1..3a72fbc66e 100644
--- a/core/src/main/java/org/apache/brooklyn/util/executor/HttpExecutorFactoryImpl.java
+++ b/core/src/main/java/org/apache/brooklyn/util/executor/HttpExecutorFactoryImpl.java
@@ -18,11 +18,10 @@
  */
 package org.apache.brooklyn.util.executor;
 
-import java.util.Map;
-import java.util.Map.Entry;
-
+import com.google.common.collect.Maps;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.core.ClassLoaderUtils;
+import org.apache.brooklyn.util.core.javalang.BrooklynHttpConfig;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.http.executor.HttpExecutor;
 import org.apache.brooklyn.util.http.executor.apacheclient.HttpExecutorImpl;
@@ -31,7 +30,8 @@ import org.apache.brooklyn.util.text.Strings;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.collect.Maps;
+import java.util.Map;
+import java.util.Map.Entry;
 
 public class HttpExecutorFactoryImpl implements HttpExecutorFactory {
     private static final Logger LOG = LoggerFactory.getLogger(HttpExecutorFactoryImpl.class);
@@ -62,7 +62,7 @@ public class HttpExecutorFactoryImpl implements HttpExecutorFactory {
 
         } else {
             LOG.info(HTTP_EXECUTOR_CLASS_CONFIG + " parameter not provided. Using the default implementation " + HttpExecutorImpl.class.getName());
-            httpExecutor = HttpExecutorImpl.newInstance();
+            httpExecutor = BrooklynHttpConfig.newHttpExecutorDefault();
         }
 
         return httpExecutor;
diff --git a/core/src/test/java/org/apache/brooklyn/util/core/ResourceUtilsTest.java b/core/src/test/java/org/apache/brooklyn/util/core/ResourceUtilsTest.java
index 11914f8aab..c45b5d3bc0 100644
--- a/core/src/test/java/org/apache/brooklyn/util/core/ResourceUtilsTest.java
+++ b/core/src/test/java/org/apache/brooklyn/util/core/ResourceUtilsTest.java
@@ -182,7 +182,7 @@ public class ResourceUtilsTest {
 
     @Test
     public void testGetResources() {
-        Iterable<URL> manifests = ResourceUtils.create().getResources("META-INF/MANIFEST.MF");
+        Iterable<URL> manifests = ResourceUtils.create("test").getResources("META-INF/MANIFEST.MF");
         assertFalse(Iterables.isEmpty(manifests));
     }
 
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/CreateUserStatements.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/CreateUserStatements.java
index d275fc102f..994fd10613 100644
--- a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/CreateUserStatements.java
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/CreateUserStatements.java
@@ -26,6 +26,7 @@ import java.util.List;
 import javax.annotation.Nullable;
 
 import org.apache.brooklyn.core.location.LocationConfigUtils;
+import org.apache.brooklyn.util.core.ResourceUtils;
 import org.apache.brooklyn.util.core.config.ConfigBag;
 import org.apache.brooklyn.util.core.crypto.SecureKeys;
 import org.apache.brooklyn.util.text.Identifiers;
@@ -129,7 +130,7 @@ public class CreateUserStatements {
                         : null;
         final boolean dontCreateUser = config.get(JcloudsLocation.DONT_CREATE_USER);
         final boolean grantUserSudo = config.get(JcloudsLocation.GRANT_USER_SUDO);
-        final LocationConfigUtils.OsCredential credential = LocationConfigUtils.getOsCredential(config);
+        final LocationConfigUtils.OsCredential credential = LocationConfigUtils.getOsCredential(config, ResourceUtils.create(location));
         credential.checkNoErrors().logAnyWarnings();
         final String passwordToSet =
                 Strings.isNonBlank(credential.getPassword()) ? credential.getPassword() : Identifiers.makeRandomId(12);
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
index a7b3d13bf3..de1b09949b 100644
--- a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
@@ -1045,7 +1045,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                     } else {
                         List<String> extraKeyDataToAuth = MutableList.of();
                         for (String keyUrl : extraKeyUrlsToAuth) {
-                            extraKeyDataToAuth.add(ResourceUtils.create().getResourceAsString(keyUrl));
+                            extraKeyDataToAuth.add(ResourceUtils.create(this).getResourceAsString(keyUrl));
                         }
                         executeCommandThrowingOnError(
                                 (SshMachineLocation)machineLocation,
@@ -1978,7 +1978,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
         }
         NodeMetadata node = Iterables.getOnlyElement(candidateNodes);
 
-        OsCredential osCredentials = LocationConfigUtils.getOsCredential(config).checkNoErrors().logAnyWarnings();
+        OsCredential osCredentials = LocationConfigUtils.getOsCredential(config, ResourceUtils.create(this)).checkNoErrors().logAnyWarnings();
         String pkd = osCredentials.getPrivateKeyData();
         String password = osCredentials.getPassword();
         LoginCredentials expectedCredentials = node.getCredentials();
@@ -2490,7 +2490,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
     protected LoginCredentials extractVmCredentials(ConfigBag setup, NodeMetadata node, LoginCredentials nodeCredentials) {
         boolean windows = isWindows(node, setup);
         String user = getUser(setup);
-        OsCredential localCredentials = LocationConfigUtils.getOsCredential(setup).checkNoErrors();
+        OsCredential localCredentials = LocationConfigUtils.getOsCredential(setup, ResourceUtils.create(this)).checkNoErrors();
         
         LOG.debug("Credentials extracted for {}: {}/{} with {}/{}", new Object[] {
                 node, user, nodeCredentials.getUser(), localCredentials, nodeCredentials });
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsSshMachineLocation.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsSshMachineLocation.java
index 68dd4924c2..199283ed61 100644
--- a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsSshMachineLocation.java
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsSshMachineLocation.java
@@ -39,6 +39,7 @@ import org.apache.brooklyn.core.location.BasicOsDetails;
 import org.apache.brooklyn.core.location.LocationConfigUtils;
 import org.apache.brooklyn.core.location.LocationConfigUtils.OsCredential;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.util.core.ResourceUtils;
 import org.apache.brooklyn.util.core.config.ResolvingConfigBag;
 import org.apache.brooklyn.util.core.flags.SetFromFlag;
 import org.apache.brooklyn.util.exceptions.Exceptions;
@@ -458,7 +459,7 @@ public class JcloudsSshMachineLocation extends SshMachineLocation implements Jcl
     }
     
     private LoginCredentials getLoginCredentials() {
-        OsCredential creds = LocationConfigUtils.getOsCredential(new ResolvingConfigBag(getManagementContext(), config().getBag()));
+        OsCredential creds = LocationConfigUtils.getOsCredential(new ResolvingConfigBag(getManagementContext(), config().getBag()), ResourceUtils.create(this));
         
         return LoginCredentials.builder()
                 .user(getUser())
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/test/entity/brooklynnode/DeployBlueprintTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/test/entity/brooklynnode/DeployBlueprintTest.java
index 87d4604bba..4d1d3b6821 100644
--- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/test/entity/brooklynnode/DeployBlueprintTest.java
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/test/entity/brooklynnode/DeployBlueprintTest.java
@@ -77,11 +77,11 @@ public class DeployBlueprintTest extends BrooklynRestResourceTest {
 
         log.info("got: "+id);
 
-        String apps = HttpTool.getContent(getEndpointAddress() + "/applications");
+        String apps = HttpTool.getContentUnsafe(getEndpointAddress() + "/applications");
         List<String> appType = parseJsonList(apps, ImmutableList.of("spec", "type"), String.class);
         assertEquals(appType, ImmutableList.of(BasicApplication.class.getName()));
 
-        String status = HttpTool.getContent(getEndpointAddress()+"/applications/"+id+"/entities/"+id+"/sensors/service.status");
+        String status = HttpTool.getContentUnsafe(getEndpointAddress()+"/applications/"+id+"/entities/"+id+"/sensors/service.status");
         log.info("STATUS: "+status);
     }
 
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTest.java
index 26c8fb012d..f887ed3566 100644
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTest.java
+++ b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTest.java
@@ -55,7 +55,7 @@ public class BrooklynRestApiLauncherTest extends BrooklynRestApiLauncherTestFixt
         int code = Asserts.succeedsEventually(new Callable<Integer>() {
             @Override
             public Integer call() throws Exception {
-                int code = HttpTool.getHttpStatusCode(rootUrl+"catalog/entities");
+                int code = HttpTool.getHttpStatusCodeUnsafe(rootUrl+"catalog/entities");
                 if (code == HttpStatus.SC_FORBIDDEN) {
                     throw new RuntimeException("Retry request");
                 } else {
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/EntityHttpClientImpl.java b/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/EntityHttpClientImpl.java
index ab08924997..c6d649126b 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/EntityHttpClientImpl.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/EntityHttpClientImpl.java
@@ -28,6 +28,7 @@ import org.apache.brooklyn.api.sensor.AttributeSensor;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
 import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.javalang.BrooklynHttpConfig;
 import org.apache.brooklyn.util.http.HttpTool;
 import org.apache.brooklyn.util.http.HttpToolResponse;
 import org.apache.brooklyn.util.core.task.Tasks;
@@ -67,10 +68,7 @@ public class EntityHttpClientImpl implements EntityHttpClient {
     @Override
     public HttpTool.HttpClientBuilder getHttpClientForBrooklynNode() {
         String baseUrl = getEntityUrl();
-        HttpTool.HttpClientBuilder builder = HttpTool.httpClientBuilder()
-                .trustAll()
-                .laxRedirect(true)
-                .uri(baseUrl);
+        HttpTool.HttpClientBuilder builder = BrooklynHttpConfig.httpClientBuilder(entity).uri(baseUrl);
         if (entity.getConfig(BrooklynNode.MANAGEMENT_USER) != null) {
             UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(
                     entity.getConfig(BrooklynNode.MANAGEMENT_USER),
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNodeIntegrationTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNodeIntegrationTest.java
index 7f2e6edab2..7506db7ead 100644
--- a/software/base/src/test/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNodeIntegrationTest.java
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNodeIntegrationTest.java
@@ -57,6 +57,7 @@ import org.apache.brooklyn.test.HttpTestUtils;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.core.ResourceUtils;
 import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.javalang.BrooklynHttpConfig;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.guava.Functionals;
 import org.apache.brooklyn.util.http.HttpAsserts;
@@ -253,7 +254,7 @@ services:
         app.start(locs);
         log.info("started "+app+" containing "+brooklynNode+" for "+JavaClassNames.niceClassAndMethod());
 
-        assertEquals(ResourceUtils.create().getResourceAsString(pseudoBrooklynCatalogFile.getAbsolutePath()), catalogContents);
+        assertEquals(ResourceUtils.create(app).getResourceAsString(pseudoBrooklynCatalogFile.getAbsolutePath()), catalogContents);
         
         // Confirm the catalog item has taken effect (by deploying an app)
         final URI webConsoleUri = brooklynNode.getAttribute(BrooklynNode.WEB_CONSOLE_URI);
@@ -263,7 +264,7 @@ services:
             .configure(DeployBlueprintEffector.BLUEPRINT_TYPE, "BrooklynNodeIntegrationTest.mycatalog:1.0")
             .getAllConfig()).get();
         
-        String apps = HttpTestUtils.getContent(webConsoleUri.toString()+"/v1/applications");
+        String apps = HttpTool.getContentUnsafe(webConsoleUri.toString()+"/v1/applications");
         List<String> appType = parseJsonList(apps, ImmutableList.of("spec", "type"), String.class);
         assertEquals(appType, ImmutableList.of(BasicApplication.class.getName()));
         
@@ -465,7 +466,7 @@ services:
 
         URI webConsoleUri = brooklynNode.getAttribute(BrooklynNode.WEB_CONSOLE_URI);
         waitForApps(webConsoleUri, 1);
-        String apps = HttpTestUtils.getContent(webConsoleUri.toString()+"/v1/applications");
+        String apps = HttpTool.getContentUnsafe(webConsoleUri.toString()+"/v1/applications");
         List<String> appType = parseJsonList(apps, ImmutableList.of("spec", "type"), String.class);
         assertEquals(appType, ImmutableList.of(BasicApplication.class.getName()));
     }
@@ -484,7 +485,7 @@ services:
             @Override
             public void run() {
                 //Wait all apps to become managed
-                String appsContent = HttpTestUtils.getContent(webConsoleUri.toString()+"/v1/applications");
+                String appsContent = HttpTool.getContentUnsafe(webConsoleUri.toString()+"/v1/applications");
                 List<String> appIds = parseJsonList(appsContent, ImmutableList.of("id"), String.class);
                 assertEquals(appIds.size(), num);
                 
@@ -510,7 +511,7 @@ services:
             .configure(DeployBlueprintEffector.BLUEPRINT_TYPE, BasicApplication.class.getName())
             .getAllConfig()).get();
         
-        String apps = HttpTestUtils.getContent(webConsoleUri.toString()+"/v1/applications");
+        String apps = HttpTool.getContentUnsafe(webConsoleUri.toString()+"/v1/applications");
         List<String> appType = parseJsonList(apps, ImmutableList.of("spec", "type"), String.class);
         assertEquals(appType, ImmutableList.of(BasicApplication.class.getName()));
         
@@ -538,17 +539,17 @@ services:
         waitForApps(webConsoleUri, 1);
 
         // Check that "mynamedloc" has been picked up from the brooklyn.properties
-        String locsContent = HttpTestUtils.getContent(webConsoleUri.toString()+"/v1/locations");
+        String locsContent = HttpTool.getContentUnsafe(webConsoleUri.toString()+"/v1/locations");
         List<String> locNames = parseJsonList(locsContent, ImmutableList.of("name"), String.class);
         assertTrue(locNames.contains("mynamedloc"), "locNames="+locNames);
 
         // Find the id of the concrete location instance of the app
-        String appsContent = HttpTestUtils.getContent(webConsoleUri.toString()+"/v1/applications");
+        String appsContent = HttpTool.getContentUnsafe(webConsoleUri.toString()+"/v1/applications");
         List<String[]> appLocationIds = parseJsonList(appsContent, ImmutableList.of("spec", "locations"), String[].class);
         String appLocationId = Iterables.getOnlyElement(appLocationIds)[0];  // app.getManagementContext().getLocationRegistry()
 
         // Check that the concrete location is of the required type
-        String locatedLocationsContent = HttpTestUtils.getContent(webConsoleUri.toString()+"/v1/locations/usage/LocatedLocations");
+        String locatedLocationsContent = HttpTool.getContentUnsafe(webConsoleUri.toString()+"/v1/locations/usage/LocatedLocations");
         assertEquals(parseJson(locatedLocationsContent, ImmutableList.of(appLocationId, "name"), String.class), "myname");
         assertEquals(parseJson(locatedLocationsContent, ImmutableList.of(appLocationId, "longitude"), Double.class), 45.6, 0.00001);
     }
@@ -576,10 +577,8 @@ services:
         Assert.assertTrue(webConsoleUri.toString().contains(""+httpsPort), "web console not using right https port ("+httpsPort+"): "+webConsoleUri);
         HttpTestUtils.assertHttpStatusCodeEquals(webConsoleUri.toString(), 401);
 
-        HttpClient http = HttpTool.httpClientBuilder()
-            .trustAll()
+        HttpClient http = BrooklynHttpConfig.httpClientBuilder(app)
             .uri(webConsoleUri)
-            .laxRedirect(true)
             .credentials(new UsernamePasswordCredentials("admin", adminPassword))
             .build();
         HttpToolResponse response = HttpTool.httpGet(http, webConsoleUri, MutableMap.<String,String>of());
@@ -653,7 +652,7 @@ services:
                 BrooklynNode.RestartSoftwareParameters.RESTART_MACHINE.getName(), "false")).getUnchecked();
 
         waitForApps(webConsoleUri.toString());
-        String apps = HttpTestUtils.getContent(webConsoleUri.toString()+"/v1/applications");
+        String apps = HttpTool.getContentUnsafe(webConsoleUri.toString()+"/v1/applications");
         List<String> appType = parseJsonList(apps, ImmutableList.of("spec", "type"), String.class);
         assertEquals(appType, ImmutableList.of(BasicApplication.class.getName()));
     }
diff --git a/test-support/src/main/java/org/apache/brooklyn/test/HttpTestUtils.java b/test-support/src/main/java/org/apache/brooklyn/test/HttpTestUtils.java
index 1006befda8..21bf1906aa 100644
--- a/test-support/src/main/java/org/apache/brooklyn/test/HttpTestUtils.java
+++ b/test-support/src/main/java/org/apache/brooklyn/test/HttpTestUtils.java
@@ -18,42 +18,30 @@
  */
 package org.apache.brooklyn.test;
 
-import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.fail;
+import com.google.common.base.Throwables;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.http.HttpTool;
+import org.apache.brooklyn.util.stream.Streams;
+import org.apache.brooklyn.util.time.Time;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.HttpURLConnection;
-import java.net.URL;
 import java.net.URLConnection;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
 
-import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLSession;
-
-import org.apache.brooklyn.util.collections.MutableMap;
-import org.apache.brooklyn.util.crypto.SslTrustUtils;
-import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.apache.brooklyn.util.http.HttpTool;
-import org.apache.brooklyn.util.http.TrustingSslSocketFactory;
-import org.apache.brooklyn.util.stream.Streams;
-import org.apache.brooklyn.util.time.Time;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.Assert;
-
-import com.google.common.base.Throwables;
-import com.google.common.collect.Lists;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
 
 /**
  * Utility methods to aid testing HTTP.
@@ -69,76 +57,20 @@ public class HttpTestUtils {
     private static final Logger LOG = LoggerFactory.getLogger(HttpTestUtils.class);
 
     static final ExecutorService executor = Executors.newCachedThreadPool();
-    
-    /**
-     * Connects to the given url and returns the connection.
-     * Caller should {@code connection.getInputStream().close()} the result of this
-     * (especially if they are making heavy use of this method).
-     */
+
+    @Deprecated /** @deprecated since 1.1, refer to (and replace per) method in HttpTool */
     public static URLConnection connectToUrl(String u) throws Exception {
-        final URL url = new URL(u);
-        final AtomicReference<Exception> exception = new AtomicReference<Exception>();
-        
-        // sometimes openConnection hangs, so run in background
-        Future<URLConnection> f = executor.submit(new Callable<URLConnection>() {
-            @Override
-            public URLConnection call() {
-                try {
-                    HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
-                        @Override public boolean verify(String s, SSLSession sslSession) {
-                            return true;
-                        }
-                    });
-                    URLConnection connection = url.openConnection();
-                    TrustingSslSocketFactory.configure(connection);
-                    connection.connect();
-    
-                    connection.getContentLength(); // Make sure the connection is made.
-                    return connection;
-                } catch (Exception e) {
-                    exception.set(e);
-                    LOG.debug("Error connecting to url "+url+" (propagating): "+e, e);
-                }
-                return null;
-            }
-        });
-        try {
-            URLConnection result = null;
-            try {
-                result = f.get(60, TimeUnit.SECONDS);
-            } catch (InterruptedException e) {
-                throw e;
-            } catch (Exception e) {
-                LOG.debug("Error connecting to url "+url+", probably timed out (rethrowing): "+e);
-                throw new IllegalStateException("Connect to URL not complete within 60 seconds, for url "+url+": "+e);
-            }
-            if (exception.get() != null) {
-                LOG.debug("Error connecting to url "+url+", thread caller of "+exception, new Throwable("source of rethrown error "+exception));
-                throw exception.get();
-            } else {
-                return result;
-            }
-        } finally {
-            f.cancel(true);
-        }
+        return HttpTool.connectToUrlUnsafe(u);
     }
 
     public static void assertHealthyStatusCode(int code) {
         if (code>=200 && code<=299) return;
         Assert.fail("Wrong status code: "+code);
     }
-    
+
+    @Deprecated /** @deprecated since 1.1, refer to (and replace per) method in HttpTool */
     public static int getHttpStatusCode(String url) throws Exception {
-        URLConnection connection = connectToUrl(url);
-        long startTime = System.currentTimeMillis();
-        int status = ((HttpURLConnection) connection).getResponseCode();
-        
-        // read fully if possible, then close everything, trying to prevent cached threads at server
-        consumeAndCloseQuietly((HttpURLConnection) connection);
-        
-        if (LOG.isDebugEnabled())
-            LOG.debug("connection to {} ({}ms) gives {}", new Object[] { url, (System.currentTimeMillis()-startTime), status });
-        return status;
+        return HttpTool.getHttpStatusCodeUnsafe(url);
     }
 
     /**
@@ -147,7 +79,7 @@ public class HttpTestUtils {
      */
     public static void assertUrlReachable(String url) {
         try {
-            getHttpStatusCode(url);
+            HttpTool.getHttpStatusCodeUnsafe(url);
         } catch (InterruptedException e) {
             Thread.currentThread().interrupt();
             throw new RuntimeException("Interrupted for "+url+" (in assertion that is reachable)", e);
@@ -158,7 +90,7 @@ public class HttpTestUtils {
 
     public static void assertUrlUnreachable(String url) {
         try {
-            int statusCode = getHttpStatusCode(url);
+            int statusCode = HttpTool.getHttpStatusCodeUnsafe(url);
             fail("Expected url "+url+" unreachable, but got status code "+statusCode);
         } catch (InterruptedException e) {
             Thread.currentThread().interrupt();
@@ -192,7 +124,7 @@ public class HttpTestUtils {
             acceptableCodes.add(code);
         }
         try {
-            int actualCode = getHttpStatusCode(url);
+            int actualCode = HttpTool.getHttpStatusCodeUnsafe(url);
             assertTrue(acceptableCodes.contains(actualCode), "code="+actualCode+"; expected="+acceptableCodes+"; url="+url);
             
         } catch (InterruptedException e) {
@@ -218,7 +150,7 @@ public class HttpTestUtils {
 
     public static void assertContentContainsText(final String url, final String phrase, final String ...additionalPhrases) {
         try {
-            String contents = getContent(url);
+            String contents = HttpTool.getContentUnsafe(url);
             Assert.assertTrue(contents != null && contents.length() > 0);
             for (String text: Lists.asList(phrase, additionalPhrases)) {
                 if (!contents.contains(text)) {
@@ -233,7 +165,7 @@ public class HttpTestUtils {
 
     public static void assertContentNotContainsText(final String url, final String phrase, final String ...additionalPhrases) {
         try {
-            String contents = getContent(url);
+            String contents = HttpTool.getContentUnsafe(url);
             Assert.assertTrue(contents != null);
             for (String text: Lists.asList(phrase, additionalPhrases)) {
                 if (contents.contains(text)) {
@@ -248,7 +180,7 @@ public class HttpTestUtils {
 
     public static void assertErrorContentContainsText(final String url, final String phrase, final String ...additionalPhrases) {
         try {
-            String contents = getErrorContent(url);
+            String contents = HttpTool.getErrorContent(url);
             Assert.assertTrue(contents != null && contents.length() > 0);
             for (String text: Lists.asList(phrase, additionalPhrases)) {
                 if (!contents.contains(text)) {
@@ -264,7 +196,7 @@ public class HttpTestUtils {
 
     public static void assertErrorContentNotContainsText(final String url, final String phrase, final String ...additionalPhrases) {
         try {
-            String err = getErrorContent(url);
+            String err = HttpTool.getErrorContent(url);
             Assert.assertTrue(err != null);
             for (String text: Lists.asList(phrase, additionalPhrases)) {
                 if (err.contains(text)) {
@@ -291,7 +223,7 @@ public class HttpTestUtils {
     }
     
     public static void assertContentMatches(String url, String regex) {
-        String contents = getContent(url);
+        String contents = HttpTool.getContentUnsafe(url);
         Assert.assertNotNull(contents);
         Assert.assertTrue(contents.matches(regex), "Contents does not match expected regex ("+regex+"): "+contents);
     }
@@ -308,7 +240,8 @@ public class HttpTestUtils {
             }
         });
     }
-    
+
+    @Deprecated /** @deprecated since 1.1, refer to (and replace per) method in HttpTool */
     public static String getErrorContent(String url) {
         try {
             HttpURLConnection connection = (HttpURLConnection) connectToUrl(url);
@@ -332,13 +265,10 @@ public class HttpTestUtils {
             throw Exceptions.propagate(e);
         }
     }
-    
+
+    @Deprecated /** @deprecated since 1.1, refer to (and replace per) method in HttpTool */
     public static String getContent(String url) {
-        try {
-            return Streams.readFullyStringAndClose(SslTrustUtils.trustAll(new URL(url).openConnection()).getInputStream());
-        } catch (Exception e) {
-            throw Throwables.propagate(e);
-        }
+        return HttpTool.getContentUnsafe(url);
     }
 
     /**
@@ -380,7 +310,7 @@ public class HttpTestUtils {
      * Ignores all exceptions completely, not even logging them!
      * 
      * Consuming the stream fully is useful for preventing idle TCP connections. 
-     * See {@linkplain http://docs.oracle.com/javase/8/docs/technotes/guides/net/http-keepalive.html}.
+     * See http://docs.oracle.com/javase/8/docs/technotes/guides/net/http-keepalive.html
      */
     public static void consumeAndCloseQuietly(HttpURLConnection connection) {
         try { Streams.readFully(connection.getInputStream()); } catch (Exception e) {}
diff --git a/test-support/src/main/java/org/apache/brooklyn/test/WebAppMonitor.java b/test-support/src/main/java/org/apache/brooklyn/test/WebAppMonitor.java
index 439013fd3b..1e59d867ed 100644
--- a/test-support/src/main/java/org/apache/brooklyn/test/WebAppMonitor.java
+++ b/test-support/src/main/java/org/apache/brooklyn/test/WebAppMonitor.java
@@ -25,6 +25,7 @@ import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.brooklyn.test.Asserts;
 import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.http.HttpTool;
 import org.apache.brooklyn.util.time.Duration;
 import org.slf4j.Logger;
 import org.testng.Assert;
@@ -80,7 +81,7 @@ public class WebAppMonitor implements Runnable {
             long startTime = System.currentTimeMillis();
             try {
                 if (preAttempt()) {
-                    int code = HttpTestUtils.getHttpStatusCode(url);
+                    int code = HttpTool.getHttpStatusCodeUnsafe(url);
                     lastTime.set(System.currentTimeMillis() - startTime);
                     lastStatus.set(code);
                     if (isResponseOkay(code)) {
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpAsserts.java b/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpAsserts.java
index b4f253866b..afa368ee8f 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpAsserts.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpAsserts.java
@@ -89,7 +89,7 @@ public class HttpAsserts {
      */
     public static void assertUrlReachable(String url) {
         try {
-            HttpTool.getHttpStatusCode(url);
+            HttpTool.getHttpStatusCodeUnsafe(url);
         } catch (InterruptedException e) {
             Thread.currentThread().interrupt();
             throw new RuntimeException("Interrupted for "+url+" (in assertion that is reachable)", e);
@@ -106,7 +106,7 @@ public class HttpAsserts {
 
     public static void assertUrlUnreachable(String url) {
         try {
-            int statusCode = HttpTool.getHttpStatusCode(url);
+            int statusCode = HttpTool.getHttpStatusCodeUnsafe(url);
             Asserts.fail("Expected url " + url + " unreachable, but got status code " + statusCode);
         } catch (InterruptedException e) {
             Thread.currentThread().interrupt();
@@ -159,7 +159,7 @@ public class HttpAsserts {
             acceptableCodes.add(code);
         }
         try {
-            int actualCode = HttpTool.getHttpStatusCode(url);
+            int actualCode = HttpTool.getHttpStatusCodeUnsafe(url);
             Asserts.assertTrue(acceptableCodes.contains(actualCode), "code=" + actualCode + "; expected=" + acceptableCodes + "; url=" + url);
             
         } catch (InterruptedException e) {
@@ -185,7 +185,7 @@ public class HttpAsserts {
 
     public static void assertContentContainsText(final String url, final String phrase, final String ...additionalPhrases) {
         try {
-            String contents = HttpTool.getContent(url);
+            String contents = HttpTool.getContentUnsafe(url);
             Asserts.assertTrue(contents != null && contents.length() > 0);
             for (String text: Lists.asList(phrase, additionalPhrases)) {
                 if (!contents.contains(text)) {
@@ -200,7 +200,7 @@ public class HttpAsserts {
 
     public static void assertContentNotContainsText(final String url, final String phrase, final String ...additionalPhrases) {
         try {
-            String contents = HttpTool.getContent(url);
+            String contents = HttpTool.getContentUnsafe(url);
             Asserts.assertTrue(contents != null);
             for (String text: Lists.asList(phrase, additionalPhrases)) {
                 if (contents.contains(text)) {
@@ -215,7 +215,7 @@ public class HttpAsserts {
 
     public static void assertErrorContentContainsText(final String url, final String phrase, final String ...additionalPhrases) {
         try {
-            String contents = HttpTool.getErrorContent(url);
+            String contents = HttpTool.getErrorContentUnsafe(url);
             Asserts.assertTrue(contents != null && contents.length() > 0);
             for (String text: Lists.asList(phrase, additionalPhrases)) {
                 if (!contents.contains(text)) {
@@ -231,7 +231,7 @@ public class HttpAsserts {
 
     public static void assertErrorContentNotContainsText(final String url, final String phrase, final String ...additionalPhrases) {
         try {
-            String err = HttpTool.getErrorContent(url);
+            String err = HttpTool.getErrorContentUnsafe(url);
             Asserts.assertTrue(err != null);
             for (String text: Lists.asList(phrase, additionalPhrases)) {
                 if (err.contains(text)) {
@@ -279,7 +279,7 @@ public class HttpAsserts {
     }
 
     public static void assertContentMatches(String url, String regex) {
-        String contents = HttpTool.getContent(url);
+        String contents = HttpTool.getContentUnsafe(url);
         Asserts.assertNotNull(contents);
         Asserts.assertTrue(contents.matches(regex), "Contents does not match expected regex ("+regex+"): "+contents);
     }
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpTool.java b/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpTool.java
index 52b8d7a435..7a3d5ac022 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpTool.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpTool.java
@@ -103,12 +103,18 @@ public class HttpTool {
 
     static final ExecutorService executor = Executors.newCachedThreadPool();
 
+    @Deprecated /** @deprecated since 1.1, use #getContentUnsafe if you don't need to trust the remote endpoint, otherwise use a more sophisticated method */
+    public static URLConnection connectToUrl(String u) throws Exception {
+        return connectToUrlUnsafe(u);
+    }
     /**
      * Connects to the given url and returns the connection.
      * Caller should {@code connection.getInputStream().close()} the result of this
      * (especially if they are making heavy use of this method).
-     */
-    public static URLConnection connectToUrl(String u) throws Exception {
+     *
+     * trusts all connections, so the content could be hacked and should not be relied upon */
+    // only used in tests and test assertions
+    public static URLConnection connectToUrlUnsafe(String u) throws Exception {
         final URL url = new URL(u);
         final AtomicReference<Exception> exception = new AtomicReference<Exception>();
 
@@ -117,16 +123,8 @@ public class HttpTool {
             @Override
             public URLConnection call() {
                 try {
-                    HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
-                        @Override
-                        public boolean verify(String s, SSLSession sslSession) {
-                            return true;
-                        }
-                    });
-                    URLConnection connection = url.openConnection();
-                    TrustingSslSocketFactory.configure(connection);
+                    URLConnection connection = SslTrustUtils.trustAll(url.openConnection());
                     connection.connect();
-
                     connection.getContentLength(); // Make sure the connection is made.
                     return connection;
                 } catch (Exception e) {
@@ -157,10 +155,14 @@ public class HttpTool {
         }
     }
 
-
-
+    @Deprecated /** @deprecated since 1.1, use #getContentUnsafe if you don't need to trust the remote endpoint, otherwise use a more sophisticated method */
     public static int getHttpStatusCode(String url) throws Exception {
-        URLConnection connection = connectToUrl(url);
+        return getHttpStatusCodeUnsafe(url);
+    }
+    /** trusts all connections, so the content could be hacked */
+    // only used in tests, test assertions, and pre-checks
+    public static int getHttpStatusCodeUnsafe(String url) throws Exception {
+        URLConnection connection = connectToUrlUnsafe(url);
         long startTime = System.currentTimeMillis();
         int status = ((HttpURLConnection) connection).getResponseCode();
 
@@ -172,8 +174,9 @@ public class HttpTool {
         return status;
     }
 
-
-    public static String getContent(String url) {
+    /** trusts all connections, so the content could be hacked */
+    // only used in tests and test assertions
+    public static String getContentUnsafe(String url) {
         try {
             return Streams.readFullyStringAndClose(SslTrustUtils.trustAll(new URL(url).openConnection()).getInputStream());
         } catch (Exception e) {
@@ -181,9 +184,20 @@ public class HttpTool {
         }
     }
 
+    @Deprecated /** @deprecated since 1.1, use #getContentUnsafe if you don't need to trust the remote endpoint, otherwise use a more sophisticated method */
+    public static String getContent(String url) {
+        return getContentUnsafe(url);
+    }
+
+    @Deprecated /** @deprecated since 1.1, use #getErrorContentUnsafe if you don't need to trust the remote endpoint, otherwise use a more sophisticated method */
     public static String getErrorContent(String url) {
+        return getErrorContentUnsafe(url);
+    }
+    /** trusts all connections, so the content could be hacked */
+    // only used in tests and test assertions
+    public static String getErrorContentUnsafe(String url) {
         try {
-            HttpURLConnection connection = (HttpURLConnection) connectToUrl(url);
+            HttpURLConnection connection = (HttpURLConnection) connectToUrlUnsafe(url);
             long startTime = System.currentTimeMillis();
 
             String err;
@@ -242,7 +256,7 @@ public class HttpTool {
     public static HttpClientBuilder httpClientBuilder() {
         return new HttpClientBuilder();
     }
-    
+
     // TODO deprecate this and use the new Apache Commons HttpClientBuilder instead
     @SuppressWarnings("deprecation")
     public static class HttpClientBuilder {
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/http/TrustingSslSocketFactory.java b/utils/common/src/main/java/org/apache/brooklyn/util/http/TrustingSslSocketFactory.java
index 6b035361cc..5968ef6e5e 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/util/http/TrustingSslSocketFactory.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/http/TrustingSslSocketFactory.java
@@ -36,8 +36,7 @@ import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Throwables;
 
-// FIXME copied from brooklyn-core because core not visible here
-
+@Deprecated /** since 1.1 use org.apache.brooklyn.util.crypto.TrustingSslSocketFactory */
 public class TrustingSslSocketFactory extends SSLSocketFactory {
     
     private static final Logger logger = LoggerFactory.getLogger(TrustingSslSocketFactory.class);
@@ -57,7 +56,8 @@ public class TrustingSslSocketFactory extends SSLSocketFactory {
         }
     }
 
-    /** configures a connection to accept all certificates, if it is for https */
+    /** configures a connection to accept all certificates, if it is for https
+     * @deprecated use {@link org.apache.brooklyn.util.crypto.SslTrustUtils#trustAll(URLConnection)} if required. */
     public static <T extends URLConnection> T configure(T connection) {
         if (connection instanceof HttpsURLConnection) {
             ((HttpsURLConnection)connection).setSSLSocketFactory(getInstance());
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/http/executor/apacheclient/HttpExecutorImpl.java b/utils/common/src/main/java/org/apache/brooklyn/util/http/executor/apacheclient/HttpExecutorImpl.java
index 7642c901ff..79cd722366 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/util/http/executor/apacheclient/HttpExecutorImpl.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/http/executor/apacheclient/HttpExecutorImpl.java
@@ -59,6 +59,13 @@ public class HttpExecutorImpl implements HttpExecutor {
     public HttpExecutorImpl() {
     }
 
+    HttpConfig config = DEFAULT_CONFIG;
+
+    public HttpExecutorImpl withConfig(HttpConfig config) {
+        this.config = config;
+        return this;
+    }
+
     @Override
     public HttpResponse execute(HttpRequest request) throws IOException {
         HttpConfig config = (request.config() != null) ? request.config() : DEFAULT_CONFIG;
diff --git a/utils/jmx/jmxmp-ssl-agent/src/test/java/org/apache/brooklyn/util/jmx/jmxmp/JmxmpClient.java b/utils/jmx/jmxmp-ssl-agent/src/test/java/org/apache/brooklyn/util/jmx/jmxmp/JmxmpClient.java
index 1d766ea6d5..b2375421e0 100644
--- a/utils/jmx/jmxmp-ssl-agent/src/test/java/org/apache/brooklyn/util/jmx/jmxmp/JmxmpClient.java
+++ b/utils/jmx/jmxmp-ssl-agent/src/test/java/org/apache/brooklyn/util/jmx/jmxmp/JmxmpClient.java
@@ -40,6 +40,7 @@ import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLSocketFactory;
 import javax.net.ssl.TrustManager;
 
+import com.google.common.base.Preconditions;
 import org.apache.brooklyn.util.core.crypto.SecureKeys;
 import org.apache.brooklyn.util.crypto.SslTrustUtils;
 import org.apache.brooklyn.util.jmx.jmxmp.JmxmpAgent;
@@ -59,12 +60,16 @@ public class JmxmpClient {
         } 
 
         jmxc.close();
-    } 
+    }
+
+    public void connectTls(String urlString, KeyStore keyStore, String keyStorePass, KeyStore trustStore) throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException, InvalidKeyException, CertificateException, SecurityException, SignatureException, IOException, KeyManagementException {
+        connectTls(urlString, keyStore, keyStorePass, SecureKeys.getTrustManager(Preconditions.checkNotNull(trustStore, "trust store must be provided or explicit TRUST ALL manager")));
+    }
 
     /** tries to connect to the given JMX url over tls, 
      * optionally using the given keystore (if null using a randomly generated key)
      * and optionally using the given truststore (if null trusting all) */
-    public void connectTls(String urlString, KeyStore keyStore, String keyStorePass, KeyStore trustStore) throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException, InvalidKeyException, CertificateException, SecurityException, SignatureException, IOException, KeyManagementException { 
+    public void connectTls(String urlString, KeyStore keyStore, String keyStorePass, TrustManager tms) throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException, InvalidKeyException, CertificateException, SecurityException, SignatureException, IOException, KeyManagementException {
         Map env = new LinkedHashMap(); 
 
         env.put("jmx.remote.profiles", JmxmpAgent.TLS_JMX_REMOTE_PROFILES);
@@ -73,8 +78,6 @@ public class JmxmpClient {
         KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); //"SunX509");
         kmf.init(keyStore, (keyStorePass!=null ? keyStorePass : "").toCharArray());
 
-        TrustManager tms = trustStore!=null ? SecureKeys.getTrustManager(trustStore) : SslTrustUtils.TRUST_ALL;
-
         SSLContext ctx = SSLContext.getInstance("TLSv1");
         ctx.init(kmf.getKeyManagers(), new TrustManager[] { tms }, null);
         SSLSocketFactory ssf = ctx.getSocketFactory(); 


[brooklyn-server] 05/06: tweak signatures so machines are preferred over entities, and short key name accepted

Posted by he...@apache.org.
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 401f26d347f2a24fd16e08c881ce7e5e7785cc35
Author: Alex Heneveld <al...@cloudsoft.io>
AuthorDate: Fri Jul 29 11:12:52 2022 +0100

    tweak signatures so machines are preferred over entities, and short key name accepted
    
    to bring it in line with other ssh config
---
 .../brooklyn/location/ssh/SshMachineLocation.java  | 10 ++---
 .../brooklyn/util/core/file/ArchiveUtils.java      |  2 +-
 .../util/core/file/BrooklynOsCommands.java         | 51 +++++++++++++++++++---
 .../brooklyn/util/core/task/ssh/SshTasks.java      |  4 +-
 .../brooklyn/location/jclouds/JcloudsLocation.java |  2 +-
 .../policy/jclouds/os/CreateUserPolicy.java        |  2 +-
 ...eJcloudsLocationUserLoginAndConfigLiveTest.java |  2 +-
 .../entity/brooklynnode/BrooklynNodeSshDriver.java |  4 +-
 .../entity/java/JavaSoftwareProcessSshDriver.java  |  4 +-
 .../brooklyn/entity/machine/MachineInitTasks.java  |  2 +-
 .../entity/machine/SetHostnameCustomizer.java      |  2 +-
 .../base/AbstractSoftwareProcessSshDriver.java     |  2 +-
 .../base/VanillaSoftwareProcessSshDriver.java      |  2 +-
 .../system_service/InitdServiceInstaller.java      |  2 +-
 .../entity/AbstractMultiDistroLiveTest.java        |  2 +-
 .../test/mysql/DynamicToyMySqlEntityBuilder.java   |  6 +--
 16 files changed, 68 insertions(+), 31 deletions(-)

diff --git a/core/src/main/java/org/apache/brooklyn/location/ssh/SshMachineLocation.java b/core/src/main/java/org/apache/brooklyn/location/ssh/SshMachineLocation.java
index 99f81a9994..9f7a8e3d1d 100644
--- a/core/src/main/java/org/apache/brooklyn/location/ssh/SshMachineLocation.java
+++ b/core/src/main/java/org/apache/brooklyn/location/ssh/SshMachineLocation.java
@@ -870,7 +870,7 @@ public class SshMachineLocation extends AbstractMachineLocation implements Machi
         LOG.debug("installing {} to {} on {}, attempting remote curl", new Object[] { url, destPath, this });
 
         try {
-            BashCommandsConfigurable bash = BrooklynOsCommands.bash(getManagementContext());
+            BashCommandsConfigurable bash = BrooklynOsCommands.bash(this);
             PipedInputStream insO = new PipedInputStream(); OutputStream outO = new PipedOutputStream(insO);
             PipedInputStream insE = new PipedInputStream(); OutputStream outE = new PipedOutputStream(insE);
             StreamGobbler sgsO = new StreamGobbler(insO, null, LOG); sgsO.setLogPrefix("[curl @ "+address+":stdout] ").start();
@@ -1063,10 +1063,10 @@ public class SshMachineLocation extends AbstractMachineLocation implements Machi
     @Override
     public String resolveOnBoxDirFor(Entity entity, String unresolvedPath) {
         ProcessTaskWrapper<Integer> baseTask = SshEffectorTasks.ssh(
-            BrooklynOsCommands.bash(entity).alternatives("mkdir -p \"${BASE_DIR}\"",
-                BrooklynOsCommands.bash(entity).chain(
-                    BrooklynOsCommands.bash(entity).sudo("mkdir -p \"${BASE_DIR}\""),
-                    BrooklynOsCommands.bash(entity).sudo("chown "+getUser()+" \"${BASE_DIR}\""))),
+            BrooklynOsCommands.bash(this).alternatives("mkdir -p \"${BASE_DIR}\"",
+                BrooklynOsCommands.bash(this).chain(
+                    BrooklynOsCommands.bash(this).sudo("mkdir -p \"${BASE_DIR}\""),
+                    BrooklynOsCommands.bash(this).sudo("chown "+getUser()+" \"${BASE_DIR}\""))),
             "cd ~",
             "cd ${BASE_DIR}",
             "echo BASE_DIR_RESULT':'`pwd`:BASE_DIR_RESULT")
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/file/ArchiveUtils.java b/core/src/main/java/org/apache/brooklyn/util/core/file/ArchiveUtils.java
index 2f16f0baed..1a5dc08a1f 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/file/ArchiveUtils.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/file/ArchiveUtils.java
@@ -286,7 +286,7 @@ public class ArchiveUtils {
             }
 
             // extract, now using task if available
-            MutableList<String> commands = MutableList.copyOf(installCommands(BrooklynOsCommands.bash(machine.getManagementContext()), destFile))
+            MutableList<String> commands = MutableList.copyOf(installCommands(BrooklynOsCommands.bash(machine), destFile))
                     .appendAll(extractCommands(destFile, tmpDir, destDir, false, keepArchiveAfterUnpacking));
             if (DynamicTasks.getTaskQueuingContext()!=null) {
                 result = DynamicTasks.queue(SshTasks.newSshExecTaskFactory(machine, commands.toArray(new String[0])).summary("extracting archive").requiringExitCodeZero()).get();
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/file/BrooklynOsCommands.java b/core/src/main/java/org/apache/brooklyn/util/core/file/BrooklynOsCommands.java
index ca636becc8..2e9a27a141 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/file/BrooklynOsCommands.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/file/BrooklynOsCommands.java
@@ -19,18 +19,27 @@
 package org.apache.brooklyn.util.core.file;
 
 import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.MachineLocation;
 import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.api.objs.BrooklynObject;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
 import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
-import org.apache.brooklyn.core.entity.EntityInternal;
 import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.core.objs.BrooklynObjectInternal;
+import org.apache.brooklyn.util.collections.MutableList;
 import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.ssh.IptablesCommandsConfigurable;
 
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
 public class BrooklynOsCommands {
 
     public static final ConfigKey<Boolean> SSH_CONFIG_SCRIPT_IGNORE_CERTS = BrooklynConfigKeys.SSH_CONFIG_SCRIPTS_IGNORE_CERTS;
+    public static final ConfigKey<Boolean> SCRIPT_IGNORE_CERTS = ConfigKeys.newConfigKeyWithPrefixRemoved(BrooklynConfigKeys.BROOKLYN_SSH_CONFIG_KEY_PREFIX, SSH_CONFIG_SCRIPT_IGNORE_CERTS);
 
     public static BashCommandsConfigurable bash(ManagementContext mgmt) {
         return BashCommandsConfigurable.newInstance().withIgnoreCerts( ((ManagementContextInternal)mgmt).getBrooklynProperties().getConfig(SSH_CONFIG_SCRIPT_IGNORE_CERTS) );
@@ -40,14 +49,42 @@ public class BrooklynOsCommands {
         return new IptablesCommandsConfigurable(bash(mgmt));
     }
 
-    public static BashCommandsConfigurable bash(Entity entity) {
-        Boolean ignoreCerts = entity.config().get(SSH_CONFIG_SCRIPT_IGNORE_CERTS);
-        if (ignoreCerts!=null) return BashCommandsConfigurable.newInstance().withIgnoreCerts(ignoreCerts);
-        return bash( ((EntityInternal)entity).getManagementContext() );
+    public static BashCommandsConfigurable bash(Entity entity, boolean includeMachineLocations) {
+        return bashForBrooklynObjects(includeMachineLocations, entity);
+    }
+
+    public static BashCommandsConfigurable bash(Location location) {
+        return bashForBrooklynObjects(false, location);
+    }
+
+    public static BashCommandsConfigurable bashForBrooklynObjects(boolean includeMachineLocations, BrooklynObject ...brooklynObjects) {
+        return bashForBrooklynObjects(includeMachineLocations, Arrays.asList(brooklynObjects));
+    }
+    public static BashCommandsConfigurable bashForBrooklynObjects(boolean includeMachineLocations, List<BrooklynObject> brooklynObjects0) {
+        ManagementContext mgmt = null;
+        List<BrooklynObject> brooklynObjects = MutableList.of();
+        for (BrooklynObject bo: brooklynObjects0) {
+            if (includeMachineLocations && bo instanceof Entity) brooklynObjects.addAll( ((Entity)bo).getLocations().stream().filter(l -> l instanceof MachineLocation).collect(Collectors.toList()) );
+
+            brooklynObjects.add(bo);
+        }
+
+        for (BrooklynObject bo: brooklynObjects) {
+            Boolean ignoreCerts = null;
+            // unprefixed key allowed for locations
+            if (bo instanceof Location) ignoreCerts = bo.config().get(SCRIPT_IGNORE_CERTS);
+
+            if (ignoreCerts==null) ignoreCerts = bo.config().get(SSH_CONFIG_SCRIPT_IGNORE_CERTS);
+            if (ignoreCerts!=null) return BashCommandsConfigurable.newInstance().withIgnoreCerts(ignoreCerts);
+
+            if (mgmt==null) mgmt = ((BrooklynObjectInternal)bo).getManagementContext();
+        }
+
+        return bash(mgmt);
     }
 
-    public static IptablesCommandsConfigurable bashIptables(Entity entity) {
-        return new IptablesCommandsConfigurable(bash(entity));
+    public static IptablesCommandsConfigurable bashIptables(BrooklynObject entityOrOtherBrooklynObject) {
+        return new IptablesCommandsConfigurable(bashForBrooklynObjects(true, entityOrOtherBrooklynObject));
     }
 
 }
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/SshTasks.java b/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/SshTasks.java
index 16066e9967..ae51d76cd7 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/SshTasks.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/SshTasks.java
@@ -184,8 +184,8 @@ public class SshTasks {
                 .commandModifier(x -> {
                     Entity entity = BrooklynTaskTags.getTargetOrContextEntity(Tasks.current());
                     BashCommandsConfigurable bash;
-                    if (entity!=null) bash = BrooklynOsCommands.bash(entity);
-                    else bash = BrooklynOsCommands.bash(machine.getManagementContext());
+                    if (entity!=null) bash = BrooklynOsCommands.bash(machine);
+                    else bash = BrooklynOsCommands.bash(machine);
                     return MutableList.of(
                         bash.dontRequireTtyForSudo(),
                         // strange quotes are to ensure we don't match against echoed stdin
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
index 764b9021e1..a7b3d13bf3 100644
--- a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
@@ -404,7 +404,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                 : (OsFamily.WINDOWS == confFamily);
     }
 
-    private BashCommandsConfigurable bashCommands() { return BrooklynOsCommands.bash(getManagementContext()); }
+    private BashCommandsConfigurable bashCommands() { return BrooklynOsCommands.bash(this); }
     private IptablesCommandsConfigurable iptablesCommands() { return new IptablesCommandsConfigurable(bashCommands()); }
 
     public boolean isLocationFirewalldEnabled(SshMachineLocation location) {
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/policy/jclouds/os/CreateUserPolicy.java b/locations/jclouds/src/main/java/org/apache/brooklyn/policy/jclouds/os/CreateUserPolicy.java
index abf0c8d8a0..300b45c1e8 100644
--- a/locations/jclouds/src/main/java/org/apache/brooklyn/policy/jclouds/os/CreateUserPolicy.java
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/policy/jclouds/os/CreateUserPolicy.java
@@ -149,7 +149,7 @@ public class CreateUserPolicy extends AbstractPolicy implements SensorEventListe
         String cmd = adminAccess.render(scriptOsFamily);
 
         // Exec command to create the user
-        BashCommandsConfigurable bash = BrooklynOsCommands.bash(entity);
+        BashCommandsConfigurable bash = BrooklynOsCommands.bash(machine);
         int result = machine.execScript(ImmutableMap.of(SshTool.PROP_RUN_AS_ROOT.getName(), true), "create-user-"+user, ImmutableList.of(cmd), ImmutableMap.of("PATH", bash.sbinPath()));
         if (result != 0) {
             throw new IllegalStateException("Failed to auto-generate user, using command "+cmd);
diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/SimpleJcloudsLocationUserLoginAndConfigLiveTest.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/SimpleJcloudsLocationUserLoginAndConfigLiveTest.java
index 230bc78f4a..980fb0dd69 100644
--- a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/SimpleJcloudsLocationUserLoginAndConfigLiveTest.java
+++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/SimpleJcloudsLocationUserLoginAndConfigLiveTest.java
@@ -215,7 +215,7 @@ public class SimpleJcloudsLocationUserLoginAndConfigLiveTest extends AbstractJcl
                 "grantUserSudo", false,
                 "waitForSshable", 30*1000));
 
-        int exitCode = execWithExitCode(m, ImmutableList.of(BrooklynOsCommands.bash(m.getManagementContext()).sudo("echo yes")));
+        int exitCode = execWithExitCode(m, ImmutableList.of(BrooklynOsCommands.bash(m).sudo("echo yes")));
         Assert.assertFalse(exitCode == 0, "exit code for sudo command should not have been 0");
     }
 
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java b/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
index 67a0d8689e..15120b570d 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
@@ -154,9 +154,9 @@ public class BrooklynNodeSshDriver extends JavaSoftwareProcessSshDriver implemen
                 getMachine().copyTo(distroStream, getInstallDir()+"/"+saveAs);
             }
         } else {
-            commands.addAll(BrooklynOsCommands.bash(getEntity()).commandsToDownloadUrlsAs(urls, saveAs));
+            commands.addAll(BrooklynOsCommands.bash(getMachine()).commandsToDownloadUrlsAs(urls, saveAs));
         }
-        commands.add(BrooklynOsCommands.bash(getEntity()).INSTALL_TAR);
+        commands.add(BrooklynOsCommands.bash(getMachine()).INSTALL_TAR);
         commands.add("tar xzfv " + saveAs);
         
         newScript(INSTALLING).
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaSoftwareProcessSshDriver.java b/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaSoftwareProcessSshDriver.java
index 636d1d35e3..54a403ac7b 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaSoftwareProcessSshDriver.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaSoftwareProcessSshDriver.java
@@ -292,7 +292,7 @@ public abstract class JavaSoftwareProcessSshDriver extends AbstractSoftwareProce
                 return true;
             }
         }
-        return tryJavaInstall(requiredVersion, BrooklynOsCommands.bash(getEntity()).installJava(requiredJavaMinor)) == 0;
+        return tryJavaInstall(requiredVersion, BrooklynOsCommands.bash(getMachine()).installJava(requiredJavaMinor)) == 0;
     }
 
     /**
@@ -417,7 +417,7 @@ public abstract class JavaSoftwareProcessSshDriver extends AbstractSoftwareProce
                     // http://mail.openjdk.java.net/pipermail/net-dev/2012-July/004603.html
                     String newHostname = "br-"+getEntity().getId().toLowerCase();
                     log.info("Detected likelihood of Java hostname bug with hostname length "+len+" for "+getEntity()+"; renaming "+getMachine()+"  to hostname "+newHostname);
-                    DynamicTasks.queue(SshEffectorTasks.ssh(BrooklynOsCommands.bash(getEntity()).setHostname(newHostname, null))).block();
+                    DynamicTasks.queue(SshEffectorTasks.ssh(BrooklynOsCommands.bash(getMachine()).setHostname(newHostname, null))).block();
                 }
             } else {
                 log.debug("Hostname length could not be determined for location "+EffectorTasks.findSshMachine()+"; not doing Java hostname bug check");
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/machine/MachineInitTasks.java b/software/base/src/main/java/org/apache/brooklyn/entity/machine/MachineInitTasks.java
index c1bbe1e63a..59be0e1642 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/machine/MachineInitTasks.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/machine/MachineInitTasks.java
@@ -70,7 +70,7 @@ public class MachineInitTasks {
         });
     }
 
-    private IptablesCommandsConfigurable iptablesCommands(SshMachineLocation m) { return new IptablesCommandsConfigurable(BrooklynOsCommands.bash(m.getManagementContext())); }
+    private IptablesCommandsConfigurable iptablesCommands(SshMachineLocation m) { return BrooklynOsCommands.bashIptables(m); }
 
     protected void stopIptablesImpl(final SshMachineLocation machine) {
 
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizer.java b/software/base/src/main/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizer.java
index 128ad9b66c..5c91698c5e 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizer.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizer.java
@@ -174,7 +174,7 @@ public class SetHostnameCustomizer extends BasicMachineLocationCustomizer {
         boolean hasDomain = Strings.isNonBlank(domainFixed);
         String fqdn = hasDomain ? hostName+"."+domainFixed : hostName;
 
-        BashCommandsConfigurable bash = BrooklynOsCommands.bash(machine.getManagementContext());
+        BashCommandsConfigurable bash = BrooklynOsCommands.bash(machine);
         exec(machine, true, 
                 bash.sudo(String.format("sed -i.bak -e '1i127.0.0.1 %s %s' -e '/^127.0.0.1/d' /etc/hosts", fqdn, hostName)),
                 bash.sudo(String.format("sed -i.bak -e 's/^HOSTNAME=.*$/HOSTNAME=%s/' /etc/sysconfig/network", fqdn)),
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessSshDriver.java b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessSshDriver.java
index 697fa6bdf0..20c3dbc282 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessSshDriver.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessSshDriver.java
@@ -362,7 +362,7 @@ public abstract class AbstractSoftwareProcessSshDriver extends AbstractSoftwareP
         return result;
     }
 
-    protected BashCommandsConfigurable bashCommands() { return BrooklynOsCommands.bash(getEntity()); }
+    protected BashCommandsConfigurable bashCommands() { return BrooklynOsCommands.bash(getMachine()); }
 
     public void checkNoHostnameBug() {
         try {
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessSshDriver.java b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessSshDriver.java
index 2cc3575075..4779a449d7 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessSshDriver.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessSshDriver.java
@@ -91,7 +91,7 @@ public class VanillaSoftwareProcessSshDriver extends AbstractSoftwareProcessSshD
             downloadedFilename = resolver.getFilename();
 
             List<String> commands = new LinkedList<String>();
-            BashCommandsConfigurable bash = BrooklynOsCommands.bash(getEntity());
+            BashCommandsConfigurable bash = BrooklynOsCommands.bash(getMachine());
             commands.addAll(bash.commandsToDownloadUrlsAs(urls, downloadedFilename));
             commands.addAll(ArchiveUtils.installCommands(bash, downloadedFilename));
 
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/system_service/InitdServiceInstaller.java b/software/base/src/main/java/org/apache/brooklyn/entity/system_service/InitdServiceInstaller.java
index 6c47b9b20b..6713dfb0ed 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/system_service/InitdServiceInstaller.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/system_service/InitdServiceInstaller.java
@@ -80,7 +80,7 @@ public class InitdServiceInstaller implements SystemServiceInstaller {
         SshPutTaskWrapper putServiceTask = SshTasks.newSshPutTaskFactory(sshMachine, tmpServicePath)
                 .contents(service)
                 .newTask();
-        BashCommandsConfigurable bash = BrooklynOsCommands.bash(sshMachine.getManagementContext());
+        BashCommandsConfigurable bash = BrooklynOsCommands.bash(sshMachine);
         ProcessTaskWrapper<Integer> installServiceTask = SshTasks.newSshExecTaskFactory(sshMachine,
                 bash.chain(
                     bash.sudo("mv " + tmpServicePath + " " + servicePath),
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/AbstractMultiDistroLiveTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/AbstractMultiDistroLiveTest.java
index 66f2485ec1..e8d54bc6c1 100644
--- a/software/base/src/test/java/org/apache/brooklyn/entity/AbstractMultiDistroLiveTest.java
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/AbstractMultiDistroLiveTest.java
@@ -144,7 +144,7 @@ public abstract class AbstractMultiDistroLiveTest extends BrooklynAppLiveTestSup
         Asserts.succeedsEventually(ImmutableMap.of("timeout", Duration.FIVE_MINUTES), new Runnable() {
             @Override
             public void run() {
-                assertExecSsh(server, ImmutableList.of(BrooklynOsCommands.bash(server).installPackage("curl"), "netstat -antp", "curl -k --retry 3 "+url));
+                assertExecSsh(server, ImmutableList.of(BrooklynOsCommands.bash(server, true).installPackage("curl"), "netstat -antp", "curl -k --retry 3 "+url));
             }});
     }
 }
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/mysql/DynamicToyMySqlEntityBuilder.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/mysql/DynamicToyMySqlEntityBuilder.java
index 98ce92a8a9..940152ce07 100644
--- a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/mysql/DynamicToyMySqlEntityBuilder.java
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/mysql/DynamicToyMySqlEntityBuilder.java
@@ -108,10 +108,10 @@ public class DynamicToyMySqlEntityBuilder {
                         SshEffectorTasks.ssh(
                             "mkdir "+dir(entity),
                             "cd "+dir(entity),
-                            BrooklynOsCommands.bash(entity).downloadToStdout(downloadUrl(entity, isLocalhost(machineS)))+" | tar xvz"
+                            BrooklynOsCommands.bash(machineS.get()).downloadToStdout(downloadUrl(entity, isLocalhost(machineS)))+" | tar xvz"
                         ).summary("download mysql").returning(SshTasks.returningStdoutLoggingInfo(log, true)));
                 if (isLinux(machineS)) {
-                    DynamicTasks.queue(SshEffectorTasks.ssh(BrooklynOsCommands.bash(entity).installPackage("libaio1")));
+                    DynamicTasks.queue(SshEffectorTasks.ssh(BrooklynOsCommands.bash(machineS.get()).installPackage("libaio1")));
                 }
                 DynamicTasks.queue(
                         SshEffectorTasks.put(".my.cnf")
@@ -127,7 +127,7 @@ public class DynamicToyMySqlEntityBuilder {
             protected void postStartCustom(ConfigBag parameters) {
                 // if it's still up after 5s assume we are good
                 Time.sleep(Duration.FIVE_SECONDS);
-                if (!DynamicTasks.queue(SshEffectorTasks.isPidFromFileRunning(BrooklynOsCommands.bash(entity()), dir(entity)+"/*/data/*.pid")).get()) {
+                if (!DynamicTasks.queue(SshEffectorTasks.isPidFromFileRunning(BrooklynOsCommands.bash(entity(), true), dir(entity)+"/*/data/*.pid")).get()) {
                     // but if it's not up add a bunch of other info
                     log.warn("MySQL did not start: "+dir(entity));
                     ProcessTaskWrapper<Integer> info = DynamicTasks.queue(SshEffectorTasks.ssh(