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:34 UTC

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

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