You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by sj...@apache.org on 2016/12/20 16:11:47 UTC

[1/7] brooklyn-server git commit: Deletions, deprecations and typos

Repository: brooklyn-server
Updated Branches:
  refs/heads/master 58b1de717 -> 770709475


Deletions, deprecations and typos

* Remove ComputeService arg from JcloudsLocation.resolveManagementHostAndPort
* Improve javadoc for Duration.of
* Deprecate Attributes.HOST_AND_PORT and delete Attributes.SERVICE_STATE
* Miscellaneous typos
* Readability of JcloudsLocation and related classes
* Delete method commented out for two years
* Remove ComputeService arg from JcloudsLocation.resolveManagementHostAndPort
* Delete unused variable from BasicJcloudsLocationCustomizer


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/2cbe309e
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/2cbe309e
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/2cbe309e

Branch: refs/heads/master
Commit: 2cbe309e741fd92776e588eb14edf7700ac9db61
Parents: 58b1de7
Author: Sam Corbett <sa...@cloudsoftcorp.com>
Authored: Thu Dec 1 15:11:24 2016 +0000
Committer: Sam Corbett <sa...@cloudsoftcorp.com>
Committed: Tue Dec 20 14:59:12 2016 +0000

----------------------------------------------------------------------
 .../brooklyn/core/config/BasicConfigKey.java    |   2 +-
 .../apache/brooklyn/core/entity/Attributes.java |  18 +-
 .../location/cloud/CloudLocationConfig.java     |   2 +-
 .../core/sensor/DependentConfiguration.java     |   2 +-
 .../location/ssh/SshMachineLocation.java        |  11 -
 .../jclouds/BasicJcloudsLocationCustomizer.java |   5 +-
 .../location/jclouds/JcloudsLocation.java       | 248 ++++++++++---------
 .../location/jclouds/JcloudsLocationConfig.java |  16 +-
 .../jclouds/JcloudsSshMachineLocation.java      |   7 +-
 .../MachineLifecycleEffectorTasks.java          |   5 +-
 .../org/apache/brooklyn/util/time/Duration.java |  13 +-
 11 files changed, 162 insertions(+), 167 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/2cbe309e/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigKey.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigKey.java b/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigKey.java
index 2600ae7..95b4bac 100644
--- a/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigKey.java
+++ b/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigKey.java
@@ -141,7 +141,7 @@ public class BasicConfigKey<T> implements ConfigKeySelfExtracting<T>, Serializab
             this.reconfigurable = val; return self();
         }
         /**
-         * @deprecated since 0.10.0; use {@link #runtime2Inheritance(ConfigInheritance)}
+         * @deprecated since 0.10.0; use {@link #runtimeInheritance(ConfigInheritance)}
          */ 
         @Deprecated
         public B parentInheritance(ConfigInheritance val) {

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/2cbe309e/core/src/main/java/org/apache/brooklyn/core/entity/Attributes.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/Attributes.java b/core/src/main/java/org/apache/brooklyn/core/entity/Attributes.java
index ba60a42..02437ef 100644
--- a/core/src/main/java/org/apache/brooklyn/core/entity/Attributes.java
+++ b/core/src/main/java/org/apache/brooklyn/core/entity/Attributes.java
@@ -95,11 +95,15 @@ public interface Attributes {
             UserAndHostAndPort.class, 
             "host.winrmAddress", 
             "user@host:port for WinRM'ing (or null if inappropriate)");
-    AttributeSensor<String> SUBNET_HOSTNAME = Sensors.newStringSensor( "host.subnet.hostname", "Host name as known internally in " +
-            "the subnet where it is running (if different to host.name)");
-    AttributeSensor<String> SUBNET_ADDRESS = Sensors.newStringSensor( "host.subnet.address", "Host address as known internally in " +
-            "the subnet where it is running (if different to host.name)");
-
+    AttributeSensor<String> SUBNET_HOSTNAME = Sensors.newStringSensor(
+            "host.subnet.hostname",
+            "Host name as known internally in the subnet where it is running (if different to host.name)");
+    AttributeSensor<String> SUBNET_ADDRESS = Sensors.newStringSensor(
+            "host.subnet.address",
+            "Host address as known internally in the subnet where it is running (if different to host.name)");
+
+    /** @deprecated since 0.11.0 without replacement */
+    @Deprecated
     AttributeSensor<String> HOST_AND_PORT = Sensors.newStringSensor( "hostandport", "host:port" );
 
     /*
@@ -134,10 +138,6 @@ public interface Attributes {
     AttributeSensor<Lifecycle.Transition> SERVICE_STATE_EXPECTED = Sensors.newSensor(Lifecycle.Transition.class,
             "service.state.expected", "Last controlled change to service state, indicating what the expected state should be");
     
-    /** @deprecated since 0.7.0 use {@link #SERVICE_STATE_ACTUAL} or {@link #SERVICE_STATE_EXPECTED} as appropriate. */
-    @Deprecated
-    AttributeSensor<Lifecycle> SERVICE_STATE = SERVICE_STATE_ACTUAL;
-
     /*
      * Other metadata (optional)
      */

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/2cbe309e/core/src/main/java/org/apache/brooklyn/core/location/cloud/CloudLocationConfig.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/location/cloud/CloudLocationConfig.java b/core/src/main/java/org/apache/brooklyn/core/location/cloud/CloudLocationConfig.java
index cc3e222..c5f3773 100644
--- a/core/src/main/java/org/apache/brooklyn/core/location/cloud/CloudLocationConfig.java
+++ b/core/src/main/java/org/apache/brooklyn/core/location/cloud/CloudLocationConfig.java
@@ -79,7 +79,7 @@ public interface CloudLocationConfig {
 
     public static final ConfigKey<String> POLL_FOR_FIRST_REACHABLE_ADDRESS = ConfigKeys.newStringConfigKey("pollForFirstReachableAddress", 
             "Whether and how long to wait for reaching the VM's ip:port; "
-            + "if 'false', will default to the node's first public IP (or privae if no public IPs); "
+            + "if 'false', will default to the node's first public IP (or private if no public IPs); "
             + "if 'true' uses default duration; otherwise accepts a time string e.g. '5m' (the default) or a number of milliseconds", "5m");
 
     @SuppressWarnings("serial")

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/2cbe309e/core/src/main/java/org/apache/brooklyn/core/sensor/DependentConfiguration.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/DependentConfiguration.java b/core/src/main/java/org/apache/brooklyn/core/sensor/DependentConfiguration.java
index 1263226..d6e0622 100644
--- a/core/src/main/java/org/apache/brooklyn/core/sensor/DependentConfiguration.java
+++ b/core/src/main/java/org/apache/brooklyn/core/sensor/DependentConfiguration.java
@@ -700,7 +700,7 @@ public class DependentConfiguration {
     public static class ProtoBuilder {
         /**
          * Will wait for the attribute on the given entity, with default behaviour:
-         * If that entity reports {@link Lifecycle#ON_FIRE} for its {@link Attributes#SERVICE_STATE} then it will abort;
+         * If that entity reports {@link Lifecycle#ON_FIRE} for its {@link Attributes#SERVICE_STATE_ACTUAL} then it will abort;
          * If that entity is stopping or destroyed (see {@link Builder#timeoutIfStoppingOrDestroyed(Duration)}),
          * then it will timeout after 1 minute.
          */

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/2cbe309e/core/src/main/java/org/apache/brooklyn/location/ssh/SshMachineLocation.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/ssh/SshMachineLocation.java b/core/src/main/java/org/apache/brooklyn/location/ssh/SshMachineLocation.java
index b611c3c..d19f08e 100644
--- a/core/src/main/java/org/apache/brooklyn/location/ssh/SshMachineLocation.java
+++ b/core/src/main/java/org/apache/brooklyn/location/ssh/SshMachineLocation.java
@@ -165,7 +165,6 @@ public class SshMachineLocation extends AbstractLocation implements MachineLocat
      * brooklyn.location.named.myLocation.sshToolClass = com.acme.brooklyn.MyCustomSshTool
      * brooklyn.location.named.myLocation.sshToolClass.myparam = myvalue
      * }
-     * }
      * </pre>
      * <p>
      */
@@ -517,16 +516,6 @@ public class SshMachineLocation extends AbstractLocation implements MachineLocat
         sshPoolCacheOrNull = null;
     }
 
-    // should not be necessary, and causes objects to be kept around a lot longer than desired
-//    @Override
-//    protected void finalize() throws Throwable {
-//        try {
-//            close();
-//        } finally {
-//            super.finalize();
-//        }
-//    }
-
     @Override
     public InetAddress getAddress() {
         return address;

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/2cbe309e/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/BasicJcloudsLocationCustomizer.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/BasicJcloudsLocationCustomizer.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/BasicJcloudsLocationCustomizer.java
index 317f4e9..dc03447 100644
--- a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/BasicJcloudsLocationCustomizer.java
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/BasicJcloudsLocationCustomizer.java
@@ -120,8 +120,6 @@ public class BasicJcloudsLocationCustomizer extends BasicConfigurableObject impl
      * @return the calling entity
      */
     protected Entity getCallerContext(JcloudsMachineLocation machine) {
-        SudoTtyFixingCustomizer s;
-
         Object context = config().get(LocationConfigKeys.CALLER_CONTEXT);
         if (context == null) {
             context = machine.config().get(LocationConfigKeys.CALLER_CONTEXT);
@@ -129,7 +127,6 @@ public class BasicJcloudsLocationCustomizer extends BasicConfigurableObject impl
         if (!(context instanceof Entity)) {
             throw new IllegalStateException("Invalid location context: " + context);
         }
-        Entity entity = (Entity) context;
-        return entity;
+        return (Entity) context;
     }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/2cbe309e/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
index 8e8d4d3..14bab2d 100644
--- a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
@@ -384,11 +384,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
     public boolean isLocationFirewalldEnabled(SshMachineLocation location) {
         int result = location.execCommands("checking if firewalld is active", 
                 ImmutableList.of(IptablesCommands.firewalldServiceIsActive()));
-        if (result == 0) {
-            return true;
-        }
-        
-        return false;
+        return result == 0;
     }
     
     protected Semaphore getMachineCreationSemaphore() {
@@ -732,11 +728,6 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                 LOG.debug("jclouds using template {} / options {} to provision machine in {}",
                         new Object[] {template, template.getOptions(), setup.getDescription()});
 
-                // no longer supported because config is sealed, we use an underlying config map
-//                if (!setup.getUnusedConfig().isEmpty())
-//                    if (LOG.isDebugEnabled())
-//                        LOG.debug("NOTE: unused flags passed to obtain VM in "+setup.getDescription()+": "
-//                                + Sanitizer.sanitize(setup.getUnusedConfig()));
                 nodes = computeService.createNodesInGroup(groupId, 1, template);
                 provisionTimestamp = Duration.of(provisioningStopwatch);
             } finally {
@@ -752,11 +743,14 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
             boolean waitForConnectable = (windows) ? waitForWinRmable : waitForSshable;
             
             if (windows) {
-                int newLoginPort = node.getLoginPort() == 22 ? (getConfig(WinRmMachineLocation.USE_HTTPS_WINRM) ? 5986 : 5985) : node.getLoginPort();
-                String newLoginUser = "root".equals(node.getCredentials().getUser()) ? "Administrator" : node.getCredentials().getUser();
-                LOG.debug("jclouds created Windows VM {}; transforming connection details: loginPort from {} to {}; loginUser from {} to {}", 
+                int newLoginPort = node.getLoginPort() == 22
+                        ? (getConfig(WinRmMachineLocation.USE_HTTPS_WINRM) ? 5986 : 5985)
+                        : node.getLoginPort();
+                String newLoginUser = "root".equals(node.getCredentials().getUser())
+                        ? "Administrator"
+                        : node.getCredentials().getUser();
+                LOG.debug("jclouds created Windows VM {}; transforming connection details: loginPort from {} to {}; loginUser from {} to {}",
                         new Object[] {node, node.getLoginPort(), newLoginPort, node.getCredentials().getUser(), newLoginUser});
-                
                 node = NodeMetadataBuilder.fromNodeMetadata(node)
                         .loginPort(newLoginPort)
                         .credentials(LoginCredentials.builder(node.getCredentials()).user(newLoginUser).build())
@@ -779,7 +773,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
             LoginCredentials initialCredentials = node.getCredentials();
             
             final HostAndPort managementHostAndPort = resolveManagementHostAndPort(
-                    computeService, node, Optional.fromNullable(userCredentials), sshHostAndPortOverride, setup,
+                    node, Optional.fromNullable(userCredentials), sshHostAndPortOverride, setup,
                     new ResolveOptions()
                             .windows(windows)
                             .expectConnectable(waitForConnectable)
@@ -831,7 +825,8 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
             if (waitForSshable && !windows) {
                 waitForSshable(computeService, node, managementHostAndPort, ImmutableList.of(userCredentials), setup);
             } else {
-                LOG.debug("Skipping ssh check for {} ({}) due to config waitForSshable=false", node, setup.getDescription());
+                LOG.debug("Skipping ssh check for {} ({}) due to config waitForSshable={}, windows={}",
+                        new Object[]{node, setup.getDescription(), waitForSshable, windows});
             }
 
             // Do not store the credentials on the node as this may leak the credentials if they
@@ -969,7 +964,10 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                                     iptablesRules.add(IptablesCommands.addFirewalldRule(Chain.INPUT, Protocol.TCP, port, Policy.ACCEPT));
                                  }
                             } else {
-                                iptablesRules = createIptablesRulesForNetworkInterface(inboundPorts);
+                                iptablesRules = Lists.newArrayList();
+                                for (Integer port : inboundPorts) {
+                                   iptablesRules.add(IptablesCommands.insertIptablesRule(Chain.INPUT, Protocol.TCP, port, Policy.ACCEPT));
+                                }
                                 iptablesRules.add(IptablesCommands.saveIptablesRules());
                             }
                             List<String> batch = Lists.newArrayList();
@@ -1250,11 +1248,11 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
 
     // ------------- constructing the template, etc ------------------------
 
-    private static interface CustomizeTemplateBuilder {
+    private interface CustomizeTemplateBuilder {
         void apply(TemplateBuilder tb, ConfigBag props, Object v);
     }
 
-    public static interface CustomizeTemplateOptions {
+    public interface CustomizeTemplateOptions {
         void apply(TemplateOptions tb, ConfigBag props, Object v);
     }
 
@@ -2003,8 +2001,10 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
      * return that fully qualified name (which we expect to be reachable from inside
      * and outside the AWS region).
      */
-    private HostAndPort resolveManagementHostAndPort(ComputeService computeService, NodeMetadata node, Optional<LoginCredentials> userCredentials, Optional<HostAndPort> hostAndPortOverride, ConfigBag config, ResolveOptions options) {
-        Boolean lookupAwsHostname = config.get(LOOKUP_AWS_HOSTNAME);
+    private HostAndPort resolveManagementHostAndPort(
+            NodeMetadata node, Optional<LoginCredentials> userCredentials,
+            Optional<HostAndPort> hostAndPortOverride, ConfigBag config, ResolveOptions options) {
+        boolean lookupAwsHostname = Boolean.TRUE.equals(config.get(LOOKUP_AWS_HOSTNAME));
         String provider = config.get(CLOUD_PROVIDER);
         if (provider == null) provider= getProvider();
         int defaultPort;
@@ -2019,7 +2019,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
             int port = hostAndPortOverride.get().hasPort() ? hostAndPortOverride.get().getPort() : defaultPort;
             return HostAndPort.fromParts(hostAndPortOverride.get().getHostText(), port);
         }
-        if (options.expectConnectable && userCredentials.isPresent() && "aws-ec2".equals(provider) && Boolean.TRUE.equals(lookupAwsHostname)) {
+        if (options.expectConnectable && userCredentials.isPresent() && "aws-ec2".equals(provider) && lookupAwsHostname) {
             // Treat AWS as a special case because the DNS fully qualified hostname in AWS is 
             // (normally?!) a good way to refer to the VM from both inside and outside of the 
             // region.
@@ -2036,7 +2036,8 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                 if (options.propagatePollForReachableFailure) {
                     throw Exceptions.propagate(e);
                 } else {
-                    LOG.warn("No reachable address ("+config.getDescription()+"/"+node+"); falling back to any advertised address; may cause future failures");
+                    LOG.warn("No reachable address ({}/{}); falling back to any advertised address; may cause future failures",
+                            config.getDescription(), node);
                 }
             }
         }
@@ -2047,8 +2048,8 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                 return HostAndPort.fromParts(address, defaultPort);
             }
         }
-        
-        LOG.warn("No resolvable address in "+addresses+" ("+config.getDescription()+"/"+node+"); using first; may cause future failures");
+        LOG.warn("No resolvable address in {} ({}/{}); using first; may cause future failures",
+                new Object[]{addresses, config.getDescription(), node});
         String host = Iterables.get(addresses, 0);
         return HostAndPort.fromParts(host, defaultPort);
     }
@@ -2378,7 +2379,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
         boolean windows = isWindows(node, setup);
         
         HostAndPort managementHostAndPort = resolveManagementHostAndPort(
-                computeService, node, Optional.<LoginCredentials>absent(), Optional.<HostAndPort>absent(), setup,
+                node, Optional.<LoginCredentials>absent(), Optional.<HostAndPort>absent(), setup,
                 new ResolveOptions()
                         .windows(windows)
                         .expectConnectable(true)
@@ -2519,9 +2520,9 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
         }
     }
 
-    protected JcloudsSshMachineLocation registerJcloudsSshMachineLocation(ComputeService computeService, NodeMetadata node, 
-            Optional<Template> template, LoginCredentials credentials, HostAndPort managementHostAndPort, ConfigBag setup) 
-            throws IOException {
+    protected JcloudsSshMachineLocation registerJcloudsSshMachineLocation(
+            ComputeService computeService, NodeMetadata node, Optional<Template> template,
+            LoginCredentials credentials, HostAndPort managementHostAndPort, ConfigBag setup) throws IOException {
         JcloudsSshMachineLocation machine = createJcloudsSshMachineLocation(computeService, node, template, credentials, managementHostAndPort, setup);
         registerJcloudsMachineLocation(node.getId(), machine);
         return machine;
@@ -2533,9 +2534,9 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
         vmInstanceIds.put(machine, nodeId);
     }
 
-    protected JcloudsSshMachineLocation createJcloudsSshMachineLocation(ComputeService computeService, NodeMetadata node, 
-            Optional<Template> template, LoginCredentials userCredentials, HostAndPort managementHostAndPort, ConfigBag setup) 
-            throws IOException {
+    protected JcloudsSshMachineLocation createJcloudsSshMachineLocation(
+            ComputeService computeService, NodeMetadata node, Optional<Template> template,
+            LoginCredentials userCredentials, HostAndPort managementHostAndPort, ConfigBag setup) throws IOException {
         Map<?,?> sshConfig = extractSshConfig(setup, node);
         String nodeAvailabilityZone = extractAvailabilityZone(setup, node);
         String nodeRegion = extractRegion(setup, node);
@@ -2544,15 +2545,16 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
             nodeRegion = extractProvider(setup, node);
         }
 
-        if (LOG.isDebugEnabled())
+        if (LOG.isDebugEnabled()) {
             LOG.debug("creating JcloudsSshMachineLocation representation for {}@{} ({}) for {}/{}",
-                    new Object[] {
+                    new Object[]{
                             getUser(setup),
                             managementHostAndPort,
                             Sanitizer.sanitize(sshConfig),
                             setup.getDescription(),
                             node
                     });
+        }
 
         String address = managementHostAndPort.getHostText();
         int port = managementHostAndPort.hasPort() ? managementHostAndPort.getPort() : node.getLoginPort();
@@ -2563,19 +2565,22 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
         // a shared IP address, for example).
         String displayName = getPublicHostnameGeneric(node, setup, Optional.of(address));
         
+        final Object password = sshConfig.get(SshMachineLocation.PASSWORD.getName()) != null
+                ? sshConfig.get(SshMachineLocation.PASSWORD.getName())
+                : userCredentials.getOptionalPassword().orNull();
+        final Object privateKeyData = sshConfig.get(SshMachineLocation.PRIVATE_KEY_DATA.getName()) != null
+                ? sshConfig.get(SshMachineLocation.PRIVATE_KEY_DATA.getName())
+                : userCredentials.getOptionalPrivateKey().orNull();
         if (isManaged()) {
-            return getManagementContext().getLocationManager().createLocation(LocationSpec.create(JcloudsSshMachineLocation.class)
+            final LocationSpec<JcloudsSshMachineLocation> spec = LocationSpec.create(JcloudsSshMachineLocation.class)
                     .configure(sshConfig)
                     .configure("displayName", displayName)
                     .configure("address", address)
                     .configure(JcloudsSshMachineLocation.SSH_PORT, port)
                     .configure("user", userCredentials.getUser())
-                    .configure(SshMachineLocation.PASSWORD.getName(), sshConfig.get(SshMachineLocation.PASSWORD.getName()) != null ?
-                            sshConfig.get(SshMachineLocation.PASSWORD.getName()) :
-                            userCredentials.getOptionalPassword().orNull())
-                    .configure(SshMachineLocation.PRIVATE_KEY_DATA.getName(), sshConfig.get(SshMachineLocation.PRIVATE_KEY_DATA.getName()) != null ?
-                            sshConfig.get(SshMachineLocation.PRIVATE_KEY_DATA.getName()) :
-                            userCredentials.getOptionalPrivateKey().orNull())
+                    // The use of `getName` is intentional. See 11741d85b9f54 for details.
+                    .configure(SshMachineLocation.PASSWORD.getName(), password)
+                    .configure(SshMachineLocation.PRIVATE_KEY_DATA.getName(), privateKeyData)
                     .configure("jcloudsParent", this)
                     .configure("node", node)
                     .configure("template", template.orNull())
@@ -2586,42 +2591,43 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                     .configureIfNotNull(SshMachineLocation.SCRIPT_DIR, setup.get(SshMachineLocation.SCRIPT_DIR))
                     .configureIfNotNull(USE_PORT_FORWARDING, setup.get(USE_PORT_FORWARDING))
                     .configureIfNotNull(PORT_FORWARDER, setup.get(PORT_FORWARDER))
-                    .configureIfNotNull(PORT_FORWARDING_MANAGER, setup.get(PORT_FORWARDING_MANAGER)));
+                    .configureIfNotNull(PORT_FORWARDING_MANAGER, setup.get(PORT_FORWARDING_MANAGER))
+                    .configureIfNotNull(SshMachineLocation.PRIVATE_ADDRESSES, node.getPrivateAddresses());
+            return getManagementContext().getLocationManager().createLocation(spec);
         } else {
-            LOG.warn("Using deprecated JcloudsSshMachineLocation constructor because "+this+" is not managed");
-            return new JcloudsSshMachineLocation(MutableMap.builder()
+            LOG.warn("Using deprecated JcloudsSshMachineLocation constructor because " + this + " is not managed");
+            final MutableMap<Object, Object> properties = MutableMap.builder()
                     .putAll(sshConfig)
                     .put("displayName", displayName)
                     .put("address", address)
                     .put("port", port)
                     .put("user", userCredentials.getUser())
-                    .putIfNotNull(SshMachineLocation.PASSWORD.getName(), sshConfig.get(SshMachineLocation.PASSWORD.getName()) != null ?
-                            SshMachineLocation.PASSWORD.getName() :
-                            userCredentials.getOptionalPassword().orNull())
-                    .putIfNotNull(SshMachineLocation.PRIVATE_KEY_DATA.getName(), sshConfig.get(SshMachineLocation.PRIVATE_KEY_DATA.getName()) != null ?
-                            SshMachineLocation.PRIVATE_KEY_DATA.getName() :
-                            userCredentials.getOptionalPrivateKey().orNull())
+                    // The use of `getName` is intentional. See 11741d85b9f54 for details.
+                    .putIfNotNull(SshMachineLocation.PASSWORD.getName(), password)
+                    .putIfNotNull(SshMachineLocation.PRIVATE_KEY_DATA.getName(), privateKeyData)
                     .put("callerContext", setup.get(CALLER_CONTEXT))
                     .putIfNotNull(CLOUD_AVAILABILITY_ZONE_ID.getName(), nodeAvailabilityZone)
                     .putIfNotNull(CLOUD_REGION_ID.getName(), nodeRegion)
                     .put(USE_PORT_FORWARDING, setup.get(USE_PORT_FORWARDING))
                     .put(PORT_FORWARDER, setup.get(PORT_FORWARDER))
                     .put(PORT_FORWARDING_MANAGER, setup.get(PORT_FORWARDING_MANAGER))
-                    .build(),
-                    this,
-                    node);
+                    .put(SshMachineLocation.PRIVATE_ADDRESSES, node.getPrivateAddresses())
+                    .build();
+            return new JcloudsSshMachineLocation(properties, this, node);
         }
     }
 
-    protected JcloudsWinRmMachineLocation registerWinRmMachineLocation(ComputeService computeService, NodeMetadata node, 
-            Optional<Template> template, LoginCredentials credentials, HostAndPort managementHostAndPort, ConfigBag setup) {
+    protected JcloudsWinRmMachineLocation registerWinRmMachineLocation(
+            ComputeService computeService, NodeMetadata node, Optional<Template> template,
+            LoginCredentials credentials, HostAndPort managementHostAndPort, ConfigBag setup) {
         JcloudsWinRmMachineLocation machine = createWinRmMachineLocation(computeService, node, template, credentials, managementHostAndPort, setup);
         registerJcloudsMachineLocation(node.getId(), machine);
         return machine;
     }
 
-    protected JcloudsWinRmMachineLocation createWinRmMachineLocation(ComputeService computeService, NodeMetadata node, 
-            Optional<Template> template, LoginCredentials userCredentials, HostAndPort winrmHostAndPort, ConfigBag setup) {
+    protected JcloudsWinRmMachineLocation createWinRmMachineLocation(
+            ComputeService computeService, NodeMetadata node, Optional<Template> template,
+            LoginCredentials userCredentials, HostAndPort winrmHostAndPort, ConfigBag setup) {
         Map<?,?> winrmConfig = extractWinrmConfig(setup, node);
         String nodeAvailabilityZone = extractAvailabilityZone(setup, node);
         String nodeRegion = extractRegion(setup, node);
@@ -2634,7 +2640,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
         String displayName = getPublicHostnameGeneric(node, setup, Optional.of(address));
 
         if (isManaged()) {
-            return getManagementContext().getLocationManager().createLocation(LocationSpec.create(JcloudsWinRmMachineLocation.class)
+            final LocationSpec<JcloudsWinRmMachineLocation> spec = LocationSpec.create(JcloudsWinRmMachineLocation.class)
                     .configure(winrmConfig)
                     .configure("jcloudsParent", this)
                     .configure("displayName", displayName)
@@ -2653,14 +2659,13 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                     .configureIfNotNull(SshMachineLocation.SCRIPT_DIR, setup.get(SshMachineLocation.SCRIPT_DIR))
                     .configureIfNotNull(USE_PORT_FORWARDING, setup.get(USE_PORT_FORWARDING))
                     .configureIfNotNull(PORT_FORWARDER, setup.get(PORT_FORWARDER))
-                    .configureIfNotNull(PORT_FORWARDING_MANAGER, setup.get(PORT_FORWARDING_MANAGER)));
+                    .configureIfNotNull(PORT_FORWARDING_MANAGER, setup.get(PORT_FORWARDING_MANAGER));
+            return getManagementContext().getLocationManager().createLocation(spec);
         } else {
             throw new UnsupportedOperationException("Cannot create WinRmMachineLocation because " + this + " is not managed");
         }
     }
 
-    // -------------- give back the machines------------------
-
     protected Map<String,Object> extractSshConfig(ConfigBag setup, NodeMetadata node) {
         ConfigBag nodeConfig = new ConfigBag();
         if (node!=null && node.getCredentials() != null) {
@@ -2719,6 +2724,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
         return null;
     }
 
+    // -------------- give back the machines------------------
 
     @Override
     public void release(MachineLocation rawMachine) {
@@ -2934,6 +2940,10 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
 
     /**
      * Extracts the user that jclouds tells us about (i.e. from the jclouds node).
+     * <p>
+     * Modifies <code>setup</code> to set {@link #USER} if it is unset when the method is called or
+     * if the value in the bag is {@link #ROOT_USERNAME} and the user on the node is contained in
+     * {@link #ROOT_ALIASES}.
      */
     protected LoginCredentials extractVmCredentials(ConfigBag setup, NodeMetadata node, LoginCredentials nodeCredentials) {
         boolean windows = isWindows(node, setup);
@@ -2952,18 +2962,21 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                 setup.put(USER, user = nodeCredentials.getUser());
             }
 
-            String pkd = Strings.maybeNonBlank(localCredentials.getPrivateKeyData()).or(nodeCredentials.getOptionalPrivateKey().orNull());
-            String pwd = Strings.maybeNonBlank(localCredentials.getPassword()).or(nodeCredentials.getOptionalPassword().orNull());
+            String pkd = Strings.maybeNonBlank(localCredentials.getPrivateKeyData())
+                    .or(nodeCredentials.getOptionalPrivateKey().orNull());
+            String pwd = Strings.maybeNonBlank(localCredentials.getPassword())
+                    .or(nodeCredentials.getOptionalPassword().orNull());
             if (Strings.isBlank(user) || (Strings.isBlank(pkd) && pwd==null)) {
                 String missing = (user==null ? "user" : "credential");
                 LOG.warn("Not able to determine "+missing+" for "+this+" at "+node+"; will likely fail subsequently");
                 return null;
             } else {
                 LoginCredentials.Builder resultBuilder = LoginCredentials.builder().user(user);
-                if (pwd != null && (Strings.isBlank(pkd) || localCredentials.isUsingPassword() || windows))
+                if (pwd != null && (Strings.isBlank(pkd) || localCredentials.isUsingPassword() || windows)) {
                     resultBuilder.password(pwd);
-                else // pkd guaranteed non-blank due to above
+                } else { // pkd guaranteed non-blank due to above
                     resultBuilder.privateKey(pkd);
+                }
                 return resultBuilder.build();
             }
         }
@@ -2982,17 +2995,19 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
 
             Predicate<? super HostAndPort> pollForFirstReachableHostAndPortPredicate;
             if (setup.get(POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE) != null) {
-                LOG.debug("Using pollForFirstReachableAddress.predicate supplied from config for location " + this + " "
-                        + POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE.getName() + " : " + setup.get(POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE));
+                LOG.debug("{} polling for first reachable address with {}",
+                        this, setup.get(POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE));
                 pollForFirstReachableHostAndPortPredicate = setup.get(POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE);
             } else {
-                LOG.debug("Using pollForFirstReachableAddress.predicate.type supplied from config for location " + this + " " + POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE_TYPE.getName()
-                        + " : " + setup.get(POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE_TYPE));
+                LOG.debug("{} polling for first reachable address with instance of {}",
+                        this, POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE_TYPE.getName());
 
-                Class<? extends Predicate<? super HostAndPort>> predicateType = setup.get(POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE_TYPE);
+                Class<? extends Predicate<? super HostAndPort>> predicateType =
+                        setup.get(POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE_TYPE);
 
                 Map<String, Object> args = MutableMap.of();
-                ConfigUtils.addUnprefixedConfigKeyInConfigBack(POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE.getName() + ".", setup, args);
+                ConfigUtils.addUnprefixedConfigKeyInConfigBack(
+                        POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE.getName() + ".", setup, args);
                 try {
                     pollForFirstReachableHostAndPortPredicate = predicateType.getConstructor(Map.class).newInstance(args);
                 } catch (NoSuchMethodException|IllegalAccessException e) {
@@ -3006,12 +3021,8 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                 }
             }
 
-            try {
-                result = JcloudsUtil.getFirstReachableAddress(node, timeout, pollForFirstReachableHostAndPortPredicate);
-            } catch (Exception e) {
-                throw Exceptions.propagate("Problem instantiating reachability checker for " + this
-                        + " pollForFirstReachableHostAndPortPredicate: " + pollForFirstReachableHostAndPortPredicate, e);
-            }
+            // Throws if no suitable address is found.
+            result = JcloudsUtil.getFirstReachableAddress(node, timeout, pollForFirstReachableHostAndPortPredicate);
             LOG.debug("Using first-reachable address "+result+" for node "+node+" in "+this);
         } else {
             result = Iterables.getFirst(Iterables.concat(node.getPublicAddresses(), node.getPrivateAddresses()), null);
@@ -3048,7 +3059,8 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
         // The createTemporaryWinRmMachineLocation deals with removing that.
         ConfigBag winrmProps = ConfigBag.newInstanceCopying(setup);
 
-        final Pair<WinRmMachineLocation, LoginCredentials> machinesToTry = Pair.of(createTemporaryWinRmMachineLocation(managementHostAndPort, credentialsToTry, winrmProps), credentialsToTry);
+        final Pair<WinRmMachineLocation, LoginCredentials> machinesToTry = Pair.of(
+                createTemporaryWinRmMachineLocation(managementHostAndPort, credentialsToTry, winrmProps), credentialsToTry);
 
         try {
             Callable<Boolean> checker = new Callable<Boolean>() {
@@ -3059,46 +3071,46 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                             ImmutableList.of("echo testing"));
                     boolean success = (response.getStatusCode() == 0);
                     if (success) {
-                            credsSuccessful.set(machinesToTry.getRight());
+                        credsSuccessful.set(machinesToTry.getRight());
 
-                            String verifyWindowsUp = getConfig(WinRmMachineLocation.WAIT_WINDOWS_TO_START);
-                            if (Strings.isBlank(verifyWindowsUp) || verifyWindowsUp.equals("false")) {
-                                return true;
-                            }
+                        String verifyWindowsUp = getConfig(WinRmMachineLocation.WAIT_WINDOWS_TO_START);
+                        if (Strings.isBlank(verifyWindowsUp) || verifyWindowsUp.equals("false")) {
+                            return true;
+                        }
 
-                            Predicate<WinRmMachineLocation> machineReachable = new Predicate<WinRmMachineLocation>() {
-                                @Override
-                                public boolean apply(@Nullable WinRmMachineLocation machine) {
-                                    try {
-                                        WinRmToolResponse response = machine.executeCommand("echo testing");
-                                        int statusCode = response.getStatusCode();
-                                        return statusCode == 0;
-                                    } catch (RuntimeException e) {
-                                        if (getFirstThrowableOfType(e, IOException.class) != null || getFirstThrowableOfType(e, WebServiceException.class) != null) {
-                                            LOG.debug("WinRM Connectivity lost", e);
-                                            return false;
-                                        } else {
-                                            throw e;
-                                        }
+                        Predicate<WinRmMachineLocation> machineReachable = new Predicate<WinRmMachineLocation>() {
+                            @Override
+                            public boolean apply(@Nullable WinRmMachineLocation machine) {
+                                try {
+                                    WinRmToolResponse response = machine.executeCommand("echo testing");
+                                    int statusCode = response.getStatusCode();
+                                    return statusCode == 0;
+                                } catch (RuntimeException e) {
+                                    if (getFirstThrowableOfType(e, IOException.class) != null || getFirstThrowableOfType(e, WebServiceException.class) != null) {
+                                        LOG.debug("WinRM Connectivity lost", e);
+                                        return false;
+                                    } else {
+                                        throw e;
                                     }
                                 }
-                            };
-                            Duration verifyWindowsUpTime = Duration.of(verifyWindowsUp);
-                            boolean restartHappened = Predicates2.retry(Predicates.not(machineReachable),
+                            }
+                        };
+                        Duration verifyWindowsUpTime = Duration.of(verifyWindowsUp);
+                        boolean restartHappened = Predicates2.retry(Predicates.not(machineReachable),
+                                verifyWindowsUpTime.toMilliseconds(),
+                                Duration.FIVE_SECONDS.toMilliseconds(),
+                                Duration.THIRTY_SECONDS.toMilliseconds(),
+                                TimeUnit.MILLISECONDS).apply(machine);
+                        if (restartHappened) {
+                            LOG.info("Connectivity to the machine was lost. Probably Windows have restarted {} as part of the provisioning process.\nRetrying to connect...", machine);
+                            return Predicates2.retry(machineReachable,
                                     verifyWindowsUpTime.toMilliseconds(),
-                                    Duration.FIVE_SECONDS.toMilliseconds(),
-                                    Duration.THIRTY_SECONDS.toMilliseconds(),
+                                    Duration.of(5, TimeUnit.SECONDS).toMilliseconds(),
+                                    Duration.of(30, TimeUnit.SECONDS).toMilliseconds(),
                                     TimeUnit.MILLISECONDS).apply(machine);
-                            if (restartHappened) {
-                                LOG.info("Connectivity to the machine was lost. Probably Windows have restarted {} as part of the provisioning process.\nRetrying to connect...", machine);
-                                return Predicates2.retry(machineReachable,
-                                        verifyWindowsUpTime.toMilliseconds(),
-                                        Duration.of(5, TimeUnit.SECONDS).toMilliseconds(),
-                                        Duration.of(30, TimeUnit.SECONDS).toMilliseconds(),
-                                        TimeUnit.MILLISECONDS).apply(machine);
-                            } else {
-                                return true;
-                            }
+                        } else {
+                            return true;
+                        }
                     }
                     return false;
                 }};
@@ -3145,8 +3157,8 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
 
     protected LoginCredentials waitForSshable(final ComputeService computeService, final NodeMetadata node, HostAndPort hostAndPort, List<LoginCredentials> credentialsToTry, ConfigBag setup) {
         String waitForSshable = setup.get(WAIT_FOR_SSHABLE);
-        checkArgument(!"false".equalsIgnoreCase(waitForSshable), "waitForReachable called despite waitForSshable=%s for %s", waitForSshable, node);
-        checkArgument(credentialsToTry.size() > 0, "waitForReachable called without credentials for %s", node);
+        checkArgument(!"false".equalsIgnoreCase(waitForSshable), "waitForSshable called despite waitForSshable=%s for %s", waitForSshable, node);
+        checkArgument(credentialsToTry.size() > 0, "waitForSshable called without credentials for %s", node);
 
         Duration timeout = null;
         try {
@@ -3592,14 +3604,6 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
         }
     }
 
-    private List<String> createIptablesRulesForNetworkInterface(Iterable<Integer> ports) {
-       List<String> iptablesRules = Lists.newArrayList();
-       for (Integer port : ports) {
-          iptablesRules.add(IptablesCommands.insertIptablesRule(Chain.INPUT, Protocol.TCP, port, Policy.ACCEPT));
-       }
-       return iptablesRules;
-    }
-
     @Override
     public PersistenceObjectStore newPersistenceObjectStore(String container) {
         return new JcloudsBlobStoreBasedObjectStore(this, container);

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/2cbe309e/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocationConfig.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocationConfig.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocationConfig.java
index 7e72b2d..eb47a8f 100644
--- a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocationConfig.java
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocationConfig.java
@@ -257,8 +257,7 @@ public interface JcloudsLocationConfig extends CloudLocationConfig {
     public static final ConfigKey<JcloudsPortForwarderExtension> PORT_FORWARDER = ConfigKeys.newConfigKey(
             JcloudsPortForwarderExtension.class, "portforwarding.forwarder", "The port-forwarder to use");
     
-    public static final ConfigKey<PortForwardManager> PORT_FORWARDING_MANAGER = BrooklynAccessUtils
-            .PORT_FORWARDING_MANAGER;
+    public static final ConfigKey<PortForwardManager> PORT_FORWARDING_MANAGER = BrooklynAccessUtils.PORT_FORWARDING_MANAGER;
 
     public static final ConfigKey<Integer> MACHINE_CREATE_ATTEMPTS = ConfigKeys.newIntegerConfigKey(
             "machineCreateAttempts", "Number of times to retry if jclouds fails to create a VM", 2);
@@ -293,10 +292,10 @@ public interface JcloudsLocationConfig extends CloudLocationConfig {
     public static final ConfigKey<Map<String,Object>> TEMPLATE_OPTIONS = ConfigKeys.newConfigKey(
             new TypeToken<Map<String, Object>>() {}, "templateOptions", "Additional jclouds template options");
 
-    /*
-         The purpose of this config is to aid deployment of blueprints to clouds where it is difficult to get nodes to
-         communicate via private IPs. This config allows a user to overwrite private IPs as public IPs, thus ensuring
-         that any blueprints they wish to deploy which may use private IPs still work in these clouds.
+    /**
+     * The purpose of this config is to aid deployment of blueprints to clouds where it is difficult to get nodes to
+     * communicate via private IPs. This config allows a user to overwrite private IPs as public IPs, thus ensuring
+     * that any blueprints they wish to deploy which may use private IPs still work in these clouds.
      */
     @Beta
     public static final ConfigKey<Boolean> USE_MACHINE_PUBLIC_ADDRESS_AS_PRIVATE_ADDRESS = ConfigKeys.newBooleanConfigKey(
@@ -304,9 +303,4 @@ public interface JcloudsLocationConfig extends CloudLocationConfig {
             "When true we will use the public IP/Hostname of a JClouds Location as the private IP/Hostname",
             false);
 
-    // TODO
-    
-//  "noDefaultSshKeys" - hints that local ssh keys should not be read as defaults
-    // this would be useful when we need to indicate a password
-
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/2cbe309e/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsSshMachineLocation.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsSshMachineLocation.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsSshMachineLocation.java
index dd096b1..e2bed51 100644
--- a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsSshMachineLocation.java
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsSshMachineLocation.java
@@ -420,9 +420,10 @@ public class JcloudsSshMachineLocation extends SshMachineLocation implements Jcl
     protected Optional<String> getPrivateAddress() {
         for (String p : getPrivateAddresses()) {
             // disallow local only addresses
-            if (Networking.isLocalOnly(p)) continue;
-            // other things may be public or private, but either way, return it
-            return Optional.of(p);
+            if (!Networking.isLocalOnly(p)) {
+                // other things may be public or private, but either way, return it
+                return Optional.of(p);
+            }
         }
         return Optional.absent();
     }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/2cbe309e/software/base/src/main/java/org/apache/brooklyn/entity/software/base/lifecycle/MachineLifecycleEffectorTasks.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/lifecycle/MachineLifecycleEffectorTasks.java b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/lifecycle/MachineLifecycleEffectorTasks.java
index 83a34de..9a4d3b9 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/lifecycle/MachineLifecycleEffectorTasks.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/lifecycle/MachineLifecycleEffectorTasks.java
@@ -115,7 +115,7 @@ import com.google.common.reflect.TypeToken;
  *  <li> {@link #preStopCustom()}
  *  <li> {@link #postStopCustom()}
  * </ul>
- * Note methods at this level typically look after the {@link Attributes#SERVICE_STATE} sensor.
+ * Note methods at this level typically look after the {@link Attributes#SERVICE_STATE_ACTUAL} sensor.
  *
  * @since 0.6.0
  */
@@ -489,7 +489,8 @@ public abstract class MachineLifecycleEffectorTasks {
             entity().sensors().set(Attributes.ADDRESS, machine.getAddress().getHostAddress());
             if (machine instanceof SshMachineLocation) {
                 SshMachineLocation sshMachine = (SshMachineLocation) machine;
-                UserAndHostAndPort sshAddress = UserAndHostAndPort.fromParts(sshMachine.getUser(), sshMachine.getAddress().getHostName(), sshMachine.getPort());
+                UserAndHostAndPort sshAddress = UserAndHostAndPort.fromParts(
+                        sshMachine.getUser(), sshMachine.getAddress().getHostName(), sshMachine.getPort());
                 entity().sensors().set(Attributes.SSH_ADDRESS, sshAddress);
             }
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/2cbe309e/utils/common/src/main/java/org/apache/brooklyn/util/time/Duration.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/time/Duration.java b/utils/common/src/main/java/org/apache/brooklyn/util/time/Duration.java
index 473aef3..f649bef 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/util/time/Duration.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/time/Duration.java
@@ -202,8 +202,17 @@ public class Duration implements Comparable<Duration>, Serializable {
             }
         };
 
-    /** tries to convert given object to a Duration, parsing strings, treating numbers as millis, etc;
-     * throws IAE if not convertible */
+    /**
+     * Converts the given object to a Duration by attempting the following in order:
+     * <ol>
+     *     <li>return null if the object is null</li>
+     *     <li>{@link #parse(String) parse} the object as a string</li>
+     *     <li>treat numbers as milliseconds</li>
+     *     <li>obtain the elapsed time of a {@link Stopwatch}</li>
+     *     <li>invoke the object's <code>toMilliseconds</code> method, if one exists.</li>
+     * </ol>
+     * If none of these cases work the method throws an {@link IllegalArgumentException}.
+     */
     public static Duration of(Object o) {
         if (o == null) return null;
         if (o instanceof Duration) return (Duration)o;


[7/7] brooklyn-server git commit: This closes #484

Posted by sj...@apache.org.
This closes #484

Small JcloudsLocation refactor


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/77070947
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/77070947
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/77070947

Branch: refs/heads/master
Commit: 77070947595090183dfcc40bc9cc9e7254f4b4d3
Parents: 58b1de7 f89ba45
Author: Sam Corbett <sa...@cloudsoftcorp.com>
Authored: Tue Dec 20 16:11:16 2016 +0000
Committer: Sam Corbett <sa...@cloudsoftcorp.com>
Committed: Tue Dec 20 16:11:16 2016 +0000

----------------------------------------------------------------------
 .../brooklyn/core/config/BasicConfigKey.java    |    2 +-
 .../apache/brooklyn/core/entity/Attributes.java |   18 +-
 .../location/cloud/CloudLocationConfig.java     |    2 +-
 .../core/sensor/DependentConfiguration.java     |    2 +-
 .../location/ssh/SshMachineLocation.java        |   11 -
 .../jclouds/BasicJcloudsLocationCustomizer.java |    5 +-
 .../location/jclouds/CreateUserStatements.java  |  303 +++++
 .../location/jclouds/JcloudsLocation.java       | 1129 +++++-------------
 .../location/jclouds/JcloudsLocationConfig.java |   16 +-
 .../jclouds/JcloudsSshMachineLocation.java      |    7 +-
 .../jclouds/RebindToMachinePredicate.java       |   85 ++
 .../customize/AutoAssignFloatingIpOption.java   |   41 +
 .../customize/AutoCreateFloatingIpsOption.java  |   41 +
 .../customize/AutoGenerateKeypairsOption.java   |   41 +
 .../templates/customize/DomainNameOption.java   |   39 +
 .../ExtraPublicKeyDataToAuthOption.java         |   52 +
 .../templates/customize/InboundPortsOption.java |   49 +
 .../templates/customize/KeyPairOption.java      |   44 +
 .../templates/customize/LoginUserOption.java    |   31 +
 .../customize/LoginUserPasswordOption.java      |   31 +
 .../LoginUserPrivateKeyDataOption.java          |   31 +
 .../LoginUserPrivateKeyFileOption.java          |   51 +
 .../templates/customize/NetworkNameOption.java  |   65 +
 .../templates/customize/RunAsRootOption.java    |   29 +
 .../customize/SecurityGroupOption.java          |   63 +
 .../templates/customize/StringTagsOption.java   |   40 +
 .../customize/TemplateBuilderCustomizer.java    |   29 +
 .../customize/TemplateBuilderCustomizers.java   |  164 +++
 .../customize/TemplateOptionCustomizer.java     |   29 +
 .../customize/TemplateOptionCustomizers.java    |  103 ++
 .../customize/TemplateOptionsOption.java        |   55 +
 .../customize/UserDataUuencodedOption.java      |   53 +
 .../customize/UserMetadataMapOption.java        |   52 +
 .../customize/UserMetadataStringOption.java     |   80 ++
 ...ationTemplateOptionsCustomisersLiveTest.java |    3 +-
 .../location/jclouds/JcloudsLocationTest.java   |    6 +-
 .../MachineLifecycleEffectorTasks.java          |    7 +-
 .../org/apache/brooklyn/util/text/Strings.java  |   30 +-
 .../org/apache/brooklyn/util/time/Duration.java |   13 +-
 39 files changed, 1952 insertions(+), 900 deletions(-)
----------------------------------------------------------------------



[3/7] brooklyn-server git commit: Extract RebindToMachinePredicate from JcloudsLocation

Posted by sj...@apache.org.
Extract RebindToMachinePredicate from JcloudsLocation


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/e0c6e7e6
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/e0c6e7e6
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/e0c6e7e6

Branch: refs/heads/master
Commit: e0c6e7e65f3e7a4a9f5b09fc1a35ccb0c7511bfe
Parents: c93a79d
Author: Sam Corbett <sa...@cloudsoftcorp.com>
Authored: Wed Dec 7 12:06:02 2016 +0000
Committer: Sam Corbett <sa...@cloudsoftcorp.com>
Committed: Tue Dec 20 15:00:27 2016 +0000

----------------------------------------------------------------------
 .../location/jclouds/JcloudsLocation.java       | 58 -------------
 .../jclouds/RebindToMachinePredicate.java       | 85 ++++++++++++++++++++
 2 files changed, 85 insertions(+), 58 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/e0c6e7e6/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
index c680815..7b7e458 100644
--- a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
@@ -2234,64 +2234,6 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
         return new RebindToMachinePredicate(config);
     }
 
-    /**
-     * Determines whether a machine may be rebinded to by comparing the given id, hostname and region
-     * against the node's id, hostname, provider id and public addresses.
-     */
-    private static class RebindToMachinePredicate implements Predicate<ComputeMetadata> {
-
-        final String rawId;
-        final String rawHostname;
-        final String rawRegion;
-
-        public RebindToMachinePredicate(ConfigBag config) {
-            rawId = (String) config.getStringKey("id");
-            rawHostname = (String) config.getStringKey("hostname");
-            rawRegion = (String) config.getStringKey("region");
-        }
-
-        @Override
-        public boolean apply(ComputeMetadata input) {
-            // ID exact match
-            if (rawId != null) {
-                // Second is AWS format
-                if (rawId.equals(input.getId()) || rawRegion != null && (rawRegion + "/" + rawId).equals(input.getId())) {
-                    return true;
-                }
-            }
-
-            // else do node metadata lookup
-            if (input instanceof NodeMetadata) {
-                NodeMetadata node = NodeMetadata.class.cast(input);
-                if (rawHostname != null && rawHostname.equalsIgnoreCase(node.getHostname()))
-                    return true;
-                if (rawHostname != null && node.getPublicAddresses().contains(rawHostname))
-                    return true;
-                if (rawId != null && rawId.equalsIgnoreCase(node.getHostname()))
-                    return true;
-                if (rawId != null && node.getPublicAddresses().contains(rawId))
-                    return true;
-                // don't do private IPs because they might be repeated
-                if (rawId != null && rawId.equalsIgnoreCase(node.getProviderId()))
-                    return true;
-                if (rawHostname != null && rawHostname.equalsIgnoreCase(node.getProviderId()))
-                    return true;
-            }
-
-            return false;
-        }
-
-        @Override
-        public String toString() {
-            return Objects.toStringHelper(this)
-                    .omitNullValues()
-                    .add("id", rawId)
-                    .add("hostname", rawHostname)
-                    .add("region", rawRegion)
-                    .toString();
-        }
-    }
-
     protected JcloudsSshMachineLocation registerJcloudsSshMachineLocation(
             ComputeService computeService, NodeMetadata node, Optional<Template> template,
             LoginCredentials credentials, HostAndPort managementHostAndPort, ConfigBag setup) throws IOException {

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/e0c6e7e6/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/RebindToMachinePredicate.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/RebindToMachinePredicate.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/RebindToMachinePredicate.java
new file mode 100644
index 0000000..5cb6e64
--- /dev/null
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/RebindToMachinePredicate.java
@@ -0,0 +1,85 @@
+/*
+ * 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.location.jclouds;
+
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.jclouds.compute.domain.ComputeMetadata;
+import org.jclouds.compute.domain.NodeMetadata;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Predicate;
+
+/**
+ * Determines whether a machine may be rebinded to by comparing the given id, hostname and region
+ * against the node's id, hostname, provider id and public addresses.
+ */
+class RebindToMachinePredicate implements Predicate<ComputeMetadata> {
+
+    final String rawId;
+    final String rawHostname;
+    final String rawRegion;
+
+    public RebindToMachinePredicate(ConfigBag config) {
+        rawId = (String) config.getStringKey("id");
+        rawHostname = (String) config.getStringKey("hostname");
+        rawRegion = (String) config.getStringKey("region");
+    }
+
+    @Override
+    public boolean apply(ComputeMetadata input) {
+        // ID exact match
+        if (rawId != null) {
+            // Second is AWS format
+            if (rawId.equals(input.getId()) || rawRegion != null && (rawRegion + "/" + rawId).equals(input.getId())) {
+                return true;
+            }
+        }
+
+        // else do node metadata lookup
+        if (input instanceof NodeMetadata) {
+            NodeMetadata node = NodeMetadata.class.cast(input);
+            if (rawHostname != null && rawHostname.equalsIgnoreCase(node.getHostname()))
+                return true;
+            if (rawHostname != null && node.getPublicAddresses().contains(rawHostname))
+                return true;
+            if (rawId != null && rawId.equalsIgnoreCase(node.getHostname()))
+                return true;
+            if (rawId != null && node.getPublicAddresses().contains(rawId))
+                return true;
+            // don't do private IPs because they might be repeated
+            if (rawId != null && rawId.equalsIgnoreCase(node.getProviderId()))
+                return true;
+            if (rawHostname != null && rawHostname.equalsIgnoreCase(node.getProviderId()))
+                return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return Objects.toStringHelper(this)
+                .omitNullValues()
+                .add("id", rawId)
+                .add("hostname", rawHostname)
+                .add("region", rawRegion)
+                .toString();
+    }
+}


[4/7] brooklyn-server git commit: Extract CustomizeTemplateOptions and its implementations from JcloudsLocation

Posted by sj...@apache.org.
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/7582ebeb/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/UserMetadataStringOption.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/UserMetadataStringOption.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/UserMetadataStringOption.java
new file mode 100644
index 0000000..b01fdcb
--- /dev/null
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/UserMetadataStringOption.java
@@ -0,0 +1,80 @@
+/*
+ * 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.location.jclouds.templates.customize;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.text.Strings;
+import org.jclouds.compute.options.TemplateOptions;
+import org.jclouds.ec2.compute.options.EC2TemplateOptions;
+import org.jclouds.softlayer.compute.options.SoftLayerTemplateOptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class UserMetadataStringOption implements TemplateOptionCustomizer {
+    private static final Logger LOG = LoggerFactory.getLogger(UserMetadataStringOption.class);
+
+    public void apply(TemplateOptions t, ConfigBag props, Object v) {
+        if (t instanceof EC2TemplateOptions) {
+            // See AWS docs: http://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/UsingConfig_WinAMI.html#user-data-execution
+            if (v == null) return;
+            String data = v.toString();
+            if (!(data.startsWith("<script>") || data.startsWith("<powershell>"))) {
+                data = "<script> " + data + " </script>";
+            }
+            ((EC2TemplateOptions) t).userData(data.getBytes());
+        } else if (t instanceof SoftLayerTemplateOptions) {
+            ((SoftLayerTemplateOptions) t).userData(Strings.toString(v));
+        } else {
+            // Try reflection: userData(String), or guestCustomizationScript(String);
+            // the latter is used by vCloud Director.
+            Class<? extends TemplateOptions> clazz = t.getClass();
+            Method userDataMethod = null;
+            try {
+                userDataMethod = clazz.getMethod("userData", String.class);
+            } catch (SecurityException e) {
+                LOG.info("Problem reflectively inspecting methods of " + t.getClass() + " for setting userData", e);
+            } catch (NoSuchMethodException e) {
+                try {
+                    // For vCloud Director
+                    userDataMethod = clazz.getMethod("guestCustomizationScript", String.class);
+                } catch (NoSuchMethodException e2) {
+                    // expected on various other clouds
+                }
+            }
+            if (userDataMethod != null) {
+                try {
+                    userDataMethod.invoke(t, Strings.toString(v));
+                } catch (InvocationTargetException e) {
+                    LOG.info("Problem invoking " + userDataMethod.getName() + " of " + t.getClass() + ", for setting userData (rethrowing)", e);
+                    throw Exceptions.propagate(e);
+                } catch (IllegalAccessException e) {
+                    LOG.debug("Unable to reflectively invoke " + userDataMethod.getName() + " of " + t.getClass() + ", for setting userData (rethrowing)", e);
+                    throw Exceptions.propagate(e);
+                }
+            } else {
+                LOG.info("ignoring userDataString({}) in VM creation because not supported for cloud/type ({})", v, t.getClass());
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/7582ebeb/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationTemplateOptionsCustomisersLiveTest.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationTemplateOptionsCustomisersLiveTest.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationTemplateOptionsCustomisersLiveTest.java
index b47595f..475fc78 100644
--- a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationTemplateOptionsCustomisersLiveTest.java
+++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationTemplateOptionsCustomisersLiveTest.java
@@ -22,6 +22,7 @@ import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 
 import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.location.jclouds.templates.customize.TemplateOptionCustomizer;
 import org.apache.brooklyn.util.core.config.ConfigBag;
 import org.jclouds.aws.ec2.compute.AWSEC2TemplateOptions;
 import org.jclouds.compute.options.TemplateOptions;
@@ -98,7 +99,7 @@ public class JcloudsLocationTemplateOptionsCustomisersLiveTest extends AbstractJ
         checkState(locationConfig.containsKey(keyToTest),
                 "location config does not contain the key " + keyToTest.getName());
 
-        JcloudsLocation.CustomizeTemplateOptions code = JcloudsLocation.SUPPORTED_TEMPLATE_OPTIONS_PROPERTIES.get(keyToTest);
+        TemplateOptionCustomizer code = JcloudsLocation.SUPPORTED_TEMPLATE_OPTIONS_PROPERTIES.get(keyToTest);
         code.apply(templateOptions, locationConfig, locationConfig.get(keyToTest));
     }
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/7582ebeb/software/base/src/main/java/org/apache/brooklyn/entity/software/base/lifecycle/MachineLifecycleEffectorTasks.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/lifecycle/MachineLifecycleEffectorTasks.java b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/lifecycle/MachineLifecycleEffectorTasks.java
index 9a4d3b9..dcd1dde 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/lifecycle/MachineLifecycleEffectorTasks.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/lifecycle/MachineLifecycleEffectorTasks.java
@@ -491,6 +491,8 @@ public abstract class MachineLifecycleEffectorTasks {
                 SshMachineLocation sshMachine = (SshMachineLocation) machine;
                 UserAndHostAndPort sshAddress = UserAndHostAndPort.fromParts(
                         sshMachine.getUser(), sshMachine.getAddress().getHostName(), sshMachine.getPort());
+                // FIXME: Who or what is SSH_ADDRESS intended for? It's not necessarily the address that
+                // the SshMachineLocation is using for ssh connections (because it accepts SSH_HOST as an override).
                 entity().sensors().set(Attributes.SSH_ADDRESS, sshAddress);
             }
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/7582ebeb/utils/common/src/main/java/org/apache/brooklyn/util/text/Strings.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/text/Strings.java b/utils/common/src/main/java/org/apache/brooklyn/util/text/Strings.java
index 22a4f7c..db24b58 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/util/text/Strings.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/text/Strings.java
@@ -28,7 +28,6 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.StringTokenizer;
-
 import javax.annotation.Nullable;
 
 import org.apache.brooklyn.util.collections.MutableList;
@@ -44,7 +43,9 @@ import com.google.common.base.Predicate;
 import com.google.common.base.Predicates;
 import com.google.common.base.Supplier;
 import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
 import com.google.common.collect.Ordering;
 
 public class Strings {
@@ -827,6 +828,33 @@ public class Strings {
         return result;
     }
 
+    /**
+     * Tries to convert v to a list of strings.
+     * <p>
+     * If v is <code>null</code> then the method returns an empty immutable list.
+     * If v is {@link Iterable} or an <code>Object[]</code> then toString is called on its elements.
+     * If v is a {@link String} then a singleton list containing v is returned.
+     * Otherwise the method throws an {@link IllegalArgumentException}.
+     */
+    public static List<String> toStringList(Object v) {
+        if (v == null) return ImmutableList.of();
+        List<String> result = Lists.newArrayList();
+        if (v instanceof Iterable) {
+            for (Object o : (Iterable<?>) v) {
+                result.add(o.toString());
+            }
+        } else if (v instanceof Object[]) {
+            for (int i = 0; i < ((Object[]) v).length; i++) {
+                result.add(((Object[]) v)[i].toString());
+            }
+        } else if (v instanceof String) {
+            result.add((String) v);
+        } else {
+            throw new IllegalArgumentException("Invalid type for List<String>: " + v + " of type " + v.getClass());
+        }
+        return result;
+    }
+
     /** returns base repeated count times */
     public static String repeat(String base, int count) {
         if (base==null) return null;


[5/7] brooklyn-server git commit: Extract CustomizeTemplateOptions and its implementations from JcloudsLocation

Posted by sj...@apache.org.
Extract CustomizeTemplateOptions and its implementations from JcloudsLocation


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/7582ebeb
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/7582ebeb
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/7582ebeb

Branch: refs/heads/master
Commit: 7582ebeb7881478a04f82b1788c0a890a1261838
Parents: e0c6e7e
Author: Sam Corbett <sa...@cloudsoftcorp.com>
Authored: Fri Dec 9 16:46:46 2016 +0000
Committer: Sam Corbett <sa...@cloudsoftcorp.com>
Committed: Tue Dec 20 15:39:01 2016 +0000

----------------------------------------------------------------------
 .../location/jclouds/JcloudsLocation.java       | 348 +++----------------
 .../customize/AutoAssignFloatingIpOption.java   |  41 +++
 .../customize/AutoCreateFloatingIpsOption.java  |  41 +++
 .../customize/AutoGenerateKeypairsOption.java   |  41 +++
 .../templates/customize/DomainNameOption.java   |  39 +++
 .../ExtraPublicKeyDataToAuthOption.java         |  52 +++
 .../templates/customize/InboundPortsOption.java |  49 +++
 .../templates/customize/KeyPairOption.java      |  44 +++
 .../templates/customize/LoginUserOption.java    |  31 ++
 .../customize/LoginUserPasswordOption.java      |  31 ++
 .../LoginUserPrivateKeyDataOption.java          |  31 ++
 .../LoginUserPrivateKeyFileOption.java          |  51 +++
 .../templates/customize/NetworkNameOption.java  |  65 ++++
 .../templates/customize/RunAsRootOption.java    |  29 ++
 .../customize/SecurityGroupOption.java          |  63 ++++
 .../templates/customize/StringTagsOption.java   |  40 +++
 .../customize/TemplateOptionCustomizer.java     |  29 ++
 .../customize/TemplateOptionCustomizers.java    | 103 ++++++
 .../customize/TemplateOptionsOption.java        |  55 +++
 .../customize/UserDataUuencodedOption.java      |  53 +++
 .../customize/UserMetadataMapOption.java        |  52 +++
 .../customize/UserMetadataStringOption.java     |  80 +++++
 ...ationTemplateOptionsCustomisersLiveTest.java |   3 +-
 .../MachineLifecycleEffectorTasks.java          |   2 +
 .../org/apache/brooklyn/util/text/Strings.java  |  30 +-
 25 files changed, 1108 insertions(+), 295 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/7582ebeb/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
index 7b7e458..fd06a9a 100644
--- a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
@@ -29,7 +29,6 @@ import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -79,6 +78,8 @@ import org.apache.brooklyn.core.mgmt.persist.PersistenceObjectStore;
 import org.apache.brooklyn.core.mgmt.persist.jclouds.JcloudsBlobStoreBasedObjectStore;
 import org.apache.brooklyn.location.jclouds.networking.JcloudsPortForwarderExtension;
 import org.apache.brooklyn.location.jclouds.templates.PortableTemplateBuilder;
+import org.apache.brooklyn.location.jclouds.templates.customize.TemplateOptionCustomizer;
+import org.apache.brooklyn.location.jclouds.templates.customize.TemplateOptionCustomizers;
 import org.apache.brooklyn.location.jclouds.zone.AwsAvailabilityZoneExtension;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
@@ -90,7 +91,6 @@ import org.apache.brooklyn.util.core.ClassLoaderUtils;
 import org.apache.brooklyn.util.core.ResourceUtils;
 import org.apache.brooklyn.util.core.config.ConfigBag;
 import org.apache.brooklyn.util.core.config.ResolvingConfigBag;
-import org.apache.brooklyn.util.core.flags.MethodCoercions;
 import org.apache.brooklyn.util.core.flags.SetFromFlag;
 import org.apache.brooklyn.util.core.flags.TypeCoercions;
 import org.apache.brooklyn.util.core.internal.ssh.ShellTool;
@@ -114,7 +114,6 @@ import org.apache.brooklyn.util.javalang.Reflections;
 import org.apache.brooklyn.util.net.Cidr;
 import org.apache.brooklyn.util.net.Networking;
 import org.apache.brooklyn.util.net.Protocol;
-import org.apache.brooklyn.util.os.Os;
 import org.apache.brooklyn.util.repeat.Repeater;
 import org.apache.brooklyn.util.ssh.BashCommands;
 import org.apache.brooklyn.util.ssh.IptablesCommands;
@@ -129,8 +128,6 @@ import org.apache.brooklyn.util.time.Duration;
 import org.apache.brooklyn.util.time.Time;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.tuple.Pair;
-import org.jclouds.aws.ec2.compute.AWSEC2TemplateOptions;
-import org.jclouds.cloudstack.compute.options.CloudStackTemplateOptions;
 import org.jclouds.compute.ComputeService;
 import org.jclouds.compute.RunNodesException;
 import org.jclouds.compute.config.AdminAccessConfiguration;
@@ -149,8 +146,6 @@ import org.jclouds.compute.options.TemplateOptions;
 import org.jclouds.domain.Credentials;
 import org.jclouds.domain.LocationScope;
 import org.jclouds.domain.LoginCredentials;
-import org.jclouds.ec2.compute.options.EC2TemplateOptions;
-import org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOptions;
 import org.jclouds.rest.AuthorizationException;
 import org.jclouds.scriptbuilder.domain.Statement;
 import org.jclouds.scriptbuilder.domain.StatementList;
@@ -162,7 +157,6 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Charsets;
 import com.google.common.base.Function;
 import com.google.common.base.Joiner;
 import com.google.common.base.Objects;
@@ -183,7 +177,6 @@ import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import com.google.common.collect.Sets.SetView;
-import com.google.common.io.Files;
 import com.google.common.net.HostAndPort;
 
 /**
@@ -1243,12 +1236,13 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
         void apply(TemplateBuilder tb, ConfigBag props, Object v);
     }
 
-    public interface CustomizeTemplateOptions {
-        void apply(TemplateOptions tb, ConfigBag props, Object v);
+    /** @deprecated since 0.11.0 use {@link TemplateOptionCustomizer} instead */
+    @Deprecated
+    public interface CustomizeTemplateOptions extends TemplateOptionCustomizer {
     }
 
     /** properties which cause customization of the TemplateBuilder */
-    public static final Map<ConfigKey<?>,CustomizeTemplateBuilder> SUPPORTED_TEMPLATE_BUILDER_PROPERTIES = ImmutableMap.<ConfigKey<?>,CustomizeTemplateBuilder>builder()
+    public static final Map<ConfigKey<?>, CustomizeTemplateBuilder> SUPPORTED_TEMPLATE_BUILDER_PROPERTIES = ImmutableMap.<ConfigKey<?>,CustomizeTemplateBuilder>builder()
             .put(OS_64_BIT, new CustomizeTemplateBuilder() {
                     public void apply(TemplateBuilder tb, ConfigBag props, Object v) {
                         Boolean os64Bit = TypeCoercions.coerce(v, Boolean.class);
@@ -1309,261 +1303,28 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
             .build();
 
     /** properties which cause customization of the TemplateOptions */
-    public static final Map<ConfigKey<?>,CustomizeTemplateOptions> SUPPORTED_TEMPLATE_OPTIONS_PROPERTIES = ImmutableMap.<ConfigKey<?>,CustomizeTemplateOptions>builder()
-            .put(SECURITY_GROUPS, new CustomizeTemplateOptions() {
-                    public void apply(TemplateOptions t, ConfigBag props, Object v) {
-                        if (t instanceof EC2TemplateOptions) {
-                            String[] securityGroups = toStringArray(v);
-                            ((EC2TemplateOptions)t).securityGroups(securityGroups);
-                        } else if (t instanceof NovaTemplateOptions) {
-                            String[] securityGroups = toStringArray(v);
-                            ((NovaTemplateOptions)t).securityGroups(securityGroups);
-                        } else if (t instanceof SoftLayerTemplateOptions) {
-                            String[] securityGroups = toStringArray(v);
-                            ((SoftLayerTemplateOptions)t).securityGroups(securityGroups);
-                        } else if (isGoogleComputeTemplateOptions(t)) {
-                            String[] securityGroups = toStringArray(v);
-                            t.securityGroups(securityGroups);
-                        } else {
-                            LOG.info("ignoring securityGroups({}) in VM creation because not supported for cloud/type ({})", v, t.getClass());
-                        }
-                    }})
-            .put(INBOUND_PORTS, new CustomizeTemplateOptions() {
-                    public void apply(TemplateOptions t, ConfigBag props, Object v) {
-                        int[] inboundPorts = toIntPortArray(v);
-                        if (LOG.isDebugEnabled()) LOG.debug("opening inbound ports {} for cloud/type {}", Arrays.toString(inboundPorts), t.getClass());
-                        t.inboundPorts(inboundPorts);
-                    }})
-            .put(USER_METADATA_STRING, new CustomizeTemplateOptions() {
-                    public void apply(TemplateOptions t, ConfigBag props, Object v) {
-                        if (t instanceof EC2TemplateOptions) {
-                            // See AWS docs: http://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/UsingConfig_WinAMI.html#user-data-execution
-                            if (v==null) return;
-                            String data = v.toString();
-                            if (!(data.startsWith("<script>") || data.startsWith("<powershell>"))) {
-                                data = "<script> " + data + " </script>";
-                            }
-                            ((EC2TemplateOptions)t).userData(data.getBytes());
-                        } else if (t instanceof SoftLayerTemplateOptions) {
-                            ((SoftLayerTemplateOptions)t).userData(Strings.toString(v));
-                        } else {
-                            // Try reflection: userData(String), or guestCustomizationScript(String);
-                            // the latter is used by vCloud Director.
-                            Class<? extends TemplateOptions> clazz = t.getClass();
-                            Method userDataMethod = null;
-                            try {
-                                userDataMethod = clazz.getMethod("userData", String.class);
-                            } catch (SecurityException e) {
-                                LOG.info("Problem reflectively inspecting methods of "+t.getClass()+" for setting userData", e);
-                            } catch (NoSuchMethodException e) {
-                                try {
-                                    // For vCloud Director
-                                    userDataMethod = clazz.getMethod("guestCustomizationScript", String.class);
-                                } catch (NoSuchMethodException e2) {
-                                    // expected on various other clouds
-                                }
-                            }
-                            if (userDataMethod != null) {
-                                try {
-                                    userDataMethod.invoke(t, Strings.toString(v));
-                                } catch (InvocationTargetException e) {
-                                    LOG.info("Problem invoking "+userDataMethod.getName()+" of "+t.getClass()+", for setting userData (rethrowing)", e);
-                                    throw Exceptions.propagate(e);
-                                } catch (IllegalAccessException e) {
-                                    LOG.debug("Unable to reflectively invoke "+userDataMethod.getName()+" of "+t.getClass()+", for setting userData (rethrowing)", e);
-                                    throw Exceptions.propagate(e);
-                                }
-                            } else {
-                                LOG.info("ignoring userDataString({}) in VM creation because not supported for cloud/type ({})", v, t.getClass());
-                            }
-                        }
-                    }})
-            .put(USER_DATA_UUENCODED, new CustomizeTemplateOptions() {
-                    public void apply(TemplateOptions t, ConfigBag props, Object v) {
-                        if (t instanceof EC2TemplateOptions) {
-                            byte[] bytes = toByteArray(v);
-                            ((EC2TemplateOptions)t).userData(bytes);
-                        } else if (t instanceof SoftLayerTemplateOptions) {
-                            ((SoftLayerTemplateOptions)t).userData(Strings.toString(v));
-                        } else {
-                            LOG.info("ignoring userData({}) in VM creation because not supported for cloud/type ({})", v, t.getClass());
-                        }
-                    }})
-            .put(STRING_TAGS, new CustomizeTemplateOptions() {
-                    public void apply(TemplateOptions t, ConfigBag props, Object v) {
-                        List<String> tags = toListOfStrings(v);
-                        if (LOG.isDebugEnabled()) LOG.debug("setting VM tags {} for {}", tags, t);
-                        t.tags(tags);
-                    }})
-            .put(USER_METADATA_MAP, new CustomizeTemplateOptions() {
-                    public void apply(TemplateOptions t, ConfigBag props, Object v) {
-                        if (v != null) {
-                            t.userMetadata(toMapStringString(v));
-                        }
-                    }})
-            .put(EXTRA_PUBLIC_KEY_DATA_TO_AUTH, new CustomizeTemplateOptions() {
-                    public void apply(TemplateOptions t, ConfigBag props, Object v) {
-                        // this is unreliable:
-                        // * seems now (Aug 2016) to be run *before* the TO.runScript which creates the user,
-                        // so is installed for the initial login user not the created user
-                        // * not supported in GCE (it uses it as the login public key, see email to jclouds list, 29 Aug 2015)
-                        // so only works if you also overrideLoginPrivateKey
-                        // --
-                        // for this reason we also inspect these ourselves
-                        // along with EXTRA_PUBLIC_KEY_URLS_TO_AUTH
-                        // and install after creation;
-                        // --
-                        // we also do it here for legacy reasons though i (alex) can't think of any situations it's needed
-                        // --
-                        // also we warn on exceptions in case someone is dumping comments or something else
-                        try {
-                            t.authorizePublicKey(((CharSequence)v).toString());
-                        } catch (Exception e) {
-                            Exceptions.propagateIfFatal(e);
-                            LOG.warn("Error trying jclouds authorizePublicKey; will run later: "+e, e);
-                        }
-                    }})
-            .put(RUN_AS_ROOT, new CustomizeTemplateOptions() {
-                    public void apply(TemplateOptions t, ConfigBag props, Object v) {
-                        t.runAsRoot((Boolean)v);
-                    }})
-            .put(LOGIN_USER, new CustomizeTemplateOptions() {
-                    public void apply(TemplateOptions t, ConfigBag props, Object v) {
-                        if (v != null) {
-                            t.overrideLoginUser(((CharSequence)v).toString());
-                        }
-                    }})
-            .put(LOGIN_USER_PASSWORD, new CustomizeTemplateOptions() {
-                    public void apply(TemplateOptions t, ConfigBag props, Object v) {
-                        if (v != null) {
-                            t.overrideLoginPassword(((CharSequence)v).toString());
-                        }
-                    }})
-            .put(LOGIN_USER_PRIVATE_KEY_FILE, new CustomizeTemplateOptions() {
-                    public void apply(TemplateOptions t, ConfigBag props, Object v) {
-                        if (v != null) {
-                            String privateKeyFileName = ((CharSequence)v).toString();
-                            String privateKey;
-                            try {
-                                privateKey = Files.toString(new File(Os.tidyPath(privateKeyFileName)), Charsets.UTF_8);
-                            } catch (IOException e) {
-                                LOG.error(privateKeyFileName + "not found", e);
-                                throw Exceptions.propagate(e);
-                            }
-                            t.overrideLoginPrivateKey(privateKey);
-                        }
-                    }})
-            .put(LOGIN_USER_PRIVATE_KEY_DATA, new CustomizeTemplateOptions() {
-                    public void apply(TemplateOptions t, ConfigBag props, Object v) {
-                        if (v != null) {
-                            t.overrideLoginPrivateKey(((CharSequence)v).toString());
-                        }
-                    }})
-            .put(KEY_PAIR, new CustomizeTemplateOptions() {
-                    public void apply(TemplateOptions t, ConfigBag props, Object v) {
-                        if (t instanceof EC2TemplateOptions) {
-                            ((EC2TemplateOptions)t).keyPair(((CharSequence)v).toString());
-                        } else if (t instanceof NovaTemplateOptions) {
-                            ((NovaTemplateOptions)t).keyPairName(((CharSequence)v).toString());
-                        } else if (t instanceof CloudStackTemplateOptions) {
-                            ((CloudStackTemplateOptions) t).keyPair(((CharSequence) v).toString());
-                        } else {
-                            LOG.info("ignoring keyPair({}) in VM creation because not supported for cloud/type ({})", v, t);
-                        }
-                    }})
-            .put(AUTO_GENERATE_KEYPAIRS, new CustomizeTemplateOptions() {
-                    public void apply(TemplateOptions t, ConfigBag props, Object v) {
-                        if (t instanceof NovaTemplateOptions) {
-                            ((NovaTemplateOptions)t).generateKeyPair((Boolean)v);
-                        } else if (t instanceof CloudStackTemplateOptions) {
-                            ((CloudStackTemplateOptions) t).generateKeyPair((Boolean) v);
-                        } else {
-                            LOG.info("ignoring auto-generate-keypairs({}) in VM creation because not supported for cloud/type ({})", v, t);
-                        }
-                    }})
-            .put(AUTO_CREATE_FLOATING_IPS, new CustomizeTemplateOptions() {
-                    public void apply(TemplateOptions t, ConfigBag props, Object v) {
-                        LOG.warn("Using deprecated "+AUTO_CREATE_FLOATING_IPS+"; use "+AUTO_ASSIGN_FLOATING_IP+" instead");
-                        if (t instanceof NovaTemplateOptions) {
-                            ((NovaTemplateOptions)t).autoAssignFloatingIp((Boolean)v);
-                        } else {
-                            LOG.info("ignoring auto-generate-floating-ips({}) in VM creation because not supported for cloud/type ({})", v, t);
-                        }
-                    }})
-            .put(AUTO_ASSIGN_FLOATING_IP, new CustomizeTemplateOptions() {
-                    public void apply(TemplateOptions t, ConfigBag props, Object v) {
-                        if (t instanceof NovaTemplateOptions) {
-                            ((NovaTemplateOptions)t).autoAssignFloatingIp((Boolean)v);
-                        } else if (t instanceof CloudStackTemplateOptions) {
-                            ((CloudStackTemplateOptions)t).setupStaticNat((Boolean)v);
-                        } else {
-                            LOG.info("ignoring auto-assign-floating-ip({}) in VM creation because not supported for cloud/type ({})", v, t);
-                        }
-                    }})
-            .put(NETWORK_NAME, new CustomizeTemplateOptions() {
-                    public void apply(TemplateOptions t, ConfigBag props, Object v) {
-                        if (t instanceof AWSEC2TemplateOptions) {
-                            // subnet ID is the sensible interpretation of network name in EC2
-                            ((AWSEC2TemplateOptions)t).subnetId((String)v);
-
-                        } else {
-                            if (isGoogleComputeTemplateOptions(t)) {
-                                // no warning needed
-                                // we think this is the only jclouds endpoint which supports this option
-
-                            } else if (t instanceof SoftLayerTemplateOptions) {
-                                LOG.warn("networkName is not be supported in SoftLayer; use `templateOptions` with `primaryNetworkComponentNetworkVlanId` or `primaryNetworkBackendComponentNetworkVlanId`");
-                            } else if (!(t instanceof CloudStackTemplateOptions) && !(t instanceof NovaTemplateOptions)) {
-                                LOG.warn("networkName is experimental in many jclouds endpoints may not be supported in this cloud");
-                                // NB, from @andreaturli
-//                                Cloudstack uses custom securityGroupIds and networkIds not the generic networks
-//                                Openstack Nova uses securityGroupNames which is marked as @deprecated (suggests to use groups which is maybe even more confusing)
-//                                Azure supports the custom networkSecurityGroupName
-                            }
-
-                            t.networks((String)v);
-                        }
-                    }})
-            .put(DOMAIN_NAME, new CustomizeTemplateOptions() {
-                    public void apply(TemplateOptions t, ConfigBag props, Object v) {
-                        if (t instanceof SoftLayerTemplateOptions) {
-                            ((SoftLayerTemplateOptions)t).domainName(TypeCoercions.coerce(v, String.class));
-                        } else {
-                            LOG.info("ignoring domain-name({}) in VM creation because not supported for cloud/type ({})", v, t);
-                        }
-                    }})
-            .put(TEMPLATE_OPTIONS, new CustomizeTemplateOptions() {
-                @Override
-                public void apply(TemplateOptions options, ConfigBag config, Object v) {
-                    if (v == null) return;
-                    @SuppressWarnings("unchecked") Map<String, Object> optionsMap = (Map<String, Object>) v;
-                    if (optionsMap.isEmpty()) return;
-
-                    Class<? extends TemplateOptions> clazz = options.getClass();
-                    for(final Map.Entry<String, Object> option : optionsMap.entrySet()) {
-                        if (option.getValue() != null) {
-                            Maybe<?> result = MethodCoercions.tryFindAndInvokeBestMatchingMethod(options, option.getKey(), option.getValue());
-                            if(result.isAbsent()) {
-                                LOG.warn("Ignoring request to set template option {} because this is not supported by {}", new Object[] { option.getKey(), clazz.getCanonicalName() });
-                            }
-                        } else {
-                            // jclouds really doesn't like you to pass nulls; don't do it! For us,
-                            // null is the only way to remove an inherited value when the templateOptions
-                            // map is being merged.
-                            LOG.debug("Ignoring request to set template option {} because value is null", new Object[] { option.getKey(), clazz.getCanonicalName() });
-                        }
-                    }
-                }})
+    public static final Map<ConfigKey<?>, ? extends TemplateOptionCustomizer>SUPPORTED_TEMPLATE_OPTIONS_PROPERTIES = ImmutableMap.<ConfigKey<?>, TemplateOptionCustomizer>builder()
+            .put(AUTO_ASSIGN_FLOATING_IP, TemplateOptionCustomizers.autoAssignFloatingIp())
+            .put(AUTO_CREATE_FLOATING_IPS, TemplateOptionCustomizers.autoCreateFloatingIps())
+            .put(AUTO_GENERATE_KEYPAIRS, TemplateOptionCustomizers.autoGenerateKeypairs())
+            .put(DOMAIN_NAME, TemplateOptionCustomizers.domainName())
+            .put(EXTRA_PUBLIC_KEY_DATA_TO_AUTH, TemplateOptionCustomizers.extraPublicKeyDataToAuth())
+            .put(INBOUND_PORTS, TemplateOptionCustomizers.inboundPorts())
+            .put(KEY_PAIR, TemplateOptionCustomizers.keyPair())
+            .put(LOGIN_USER, TemplateOptionCustomizers.loginUser())
+            .put(LOGIN_USER_PASSWORD, TemplateOptionCustomizers.loginUserPassword())
+            .put(LOGIN_USER_PRIVATE_KEY_DATA, TemplateOptionCustomizers.loginUserPrivateKeyData())
+            .put(LOGIN_USER_PRIVATE_KEY_FILE, TemplateOptionCustomizers.loginUserPrivateKeyFile())
+            .put(NETWORK_NAME, TemplateOptionCustomizers.networkName())
+            .put(RUN_AS_ROOT, TemplateOptionCustomizers.runAsRoot())
+            .put(SECURITY_GROUPS, TemplateOptionCustomizers.securityGroups())
+            .put(STRING_TAGS, TemplateOptionCustomizers.stringTags())
+            .put(TEMPLATE_OPTIONS, TemplateOptionCustomizers.templateOptions())
+            .put(USER_DATA_UUENCODED, TemplateOptionCustomizers.userDataUuencoded())
+            .put(USER_METADATA_MAP, TemplateOptionCustomizers.userMetadataMap())
+            .put(USER_METADATA_STRING, TemplateOptionCustomizers.userMetadataString())
             .build();
 
-    /**
-     * Avoid having a dependency on googlecompute because it doesn't have an OSGi bundle yet.
-     * Fixed in jclouds 2.0.0-SNAPSHOT
-     */
-    private static boolean isGoogleComputeTemplateOptions(TemplateOptions t) {
-        return t.getClass().getName().equals("org.jclouds.googlecomputeengine.compute.options.GoogleComputeEngineTemplateOptions");
-    }
-
     /** hook whereby template customizations can be made for various clouds */
     protected void customizeTemplate(ComputeService computeService, Template template, Collection<JcloudsLocationCustomizer> customizers) {
         for (JcloudsLocationCustomizer customizer : customizers) {
@@ -1761,9 +1522,9 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
             }
         }
 
-        for (Map.Entry<ConfigKey<?>, CustomizeTemplateOptions> entry : SUPPORTED_TEMPLATE_OPTIONS_PROPERTIES.entrySet()) {
+        for (Map.Entry<ConfigKey<?>, ? extends TemplateOptionCustomizer> entry : SUPPORTED_TEMPLATE_OPTIONS_PROPERTIES.entrySet()) {
             ConfigKey<?> key = entry.getKey();
-            CustomizeTemplateOptions code = entry.getValue();
+            TemplateOptionCustomizer code = entry.getValue();
             if (config.containsKey(key) && config.get(key) != null) {
                 code.apply(options, config, config.get(key));
             }
@@ -3236,14 +2997,23 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
         }
     }
 
+    @Override
+    public PersistenceObjectStore newPersistenceObjectStore(String container) {
+        return new JcloudsBlobStoreBasedObjectStore(this, container);
+    }
+
     // ------------ static converters (could go to a new file) ------------------
 
+    /** @deprecated since 0.11.0 without replacement */
+    @Deprecated
     public static File asFile(Object o) {
         if (o instanceof File) return (File)o;
         if (o == null) return null;
         return new File(o.toString());
     }
 
+    /** @deprecated since 0.11.0 without replacement */
+    @Deprecated
     public static String fileAsString(Object o) {
         if (o instanceof String) return (String)o;
         if (o instanceof File) return ((File)o).getAbsolutePath();
@@ -3251,6 +3021,8 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
         return o.toString();
     }
 
+    /** @deprecated since 0.11.0 without replacement */
+    @Deprecated
     protected static double toDouble(Object v) {
         if (v instanceof Number) {
             return ((Number)v).doubleValue();
@@ -3259,28 +3031,20 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
         }
     }
 
+    /** @deprecated since 0.11.0 without replacement */
+    @Deprecated
     protected static String[] toStringArray(Object v) {
-        return toListOfStrings(v).toArray(new String[0]);
+        return Strings.toStringList(v).toArray(new String[0]);
     }
 
+    /** @deprecated since 0.11.0 use {@link Strings#toStringList(Object)} instead */
+    @Deprecated
     protected static List<String> toListOfStrings(Object v) {
-        List<String> result = Lists.newArrayList();
-        if (v instanceof Iterable) {
-            for (Object o : (Iterable<?>)v) {
-                result.add(o.toString());
-            }
-        } else if (v instanceof Object[]) {
-            for (int i = 0; i < ((Object[])v).length; i++) {
-                result.add(((Object[])v)[i].toString());
-            }
-        } else if (v instanceof String) {
-            result.add((String) v);
-        } else {
-            throw new IllegalArgumentException("Invalid type for List<String>: "+v+" of type "+v.getClass());
-        }
-        return result;
+        return Strings.toStringList(v);
     }
 
+    /** @deprecated since 0.11.0 without replacement */
+    @Deprecated
     protected static byte[] toByteArray(Object v) {
         if (v instanceof byte[]) {
             return (byte[]) v;
@@ -3291,21 +3055,24 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
         }
     }
 
+    /** @deprecated since 0.11.0 without replacement */
+    @Deprecated
     @VisibleForTesting
     static int[] toIntPortArray(Object v) {
         PortRange portRange = PortRanges.fromIterable(Collections.singletonList(v));
-        int[] portArray = ArrayUtils.toPrimitive(Iterables.toArray(portRange, Integer.class));
-
-        return portArray;
+        return ArrayUtils.toPrimitive(Iterables.toArray(portRange, Integer.class));
     }
 
+
+    /** @deprecated since 0.11.0 without replacement */
+    @Deprecated
     // Handles GString
     protected static Map<String,String> toMapStringString(Object v) {
         if (v instanceof Map<?,?>) {
             Map<String,String> result = Maps.newLinkedHashMap();
             for (Map.Entry<?,?> entry : ((Map<?,?>)v).entrySet()) {
-                String key = ((CharSequence)entry.getKey()).toString();
-                String value = ((CharSequence)entry.getValue()).toString();
+                String key = entry.getKey().toString();
+                String value = entry.getValue().toString();
                 result.put(key, value);
             }
             return result;
@@ -3317,11 +3084,6 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
         }
     }
 
-    @Override
-    public PersistenceObjectStore newPersistenceObjectStore(String container) {
-        return new JcloudsBlobStoreBasedObjectStore(this, container);
-    }
-
     // TODO Very similar to EntityConfigMap.deepMerge
     private <T> Maybe<?> shallowMerge(Maybe<? extends T> val1, Maybe<? extends T> val2, ConfigKey<?> keyForLogging) {
         if (val2.isAbsent() || val2.isNull()) {

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/7582ebeb/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/AutoAssignFloatingIpOption.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/AutoAssignFloatingIpOption.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/AutoAssignFloatingIpOption.java
new file mode 100644
index 0000000..0996222
--- /dev/null
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/AutoAssignFloatingIpOption.java
@@ -0,0 +1,41 @@
+/*
+ * 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.location.jclouds.templates.customize;
+
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.jclouds.cloudstack.compute.options.CloudStackTemplateOptions;
+import org.jclouds.compute.options.TemplateOptions;
+import org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class AutoAssignFloatingIpOption implements TemplateOptionCustomizer {
+    private static final Logger LOG = LoggerFactory.getLogger(AutoAssignFloatingIpOption.class);
+
+    public void apply(TemplateOptions t, ConfigBag props, Object v) {
+        if (t instanceof NovaTemplateOptions) {
+            ((NovaTemplateOptions) t).autoAssignFloatingIp((Boolean) v);
+        } else if (t instanceof CloudStackTemplateOptions) {
+            ((CloudStackTemplateOptions) t).setupStaticNat((Boolean) v);
+        } else {
+            LOG.info("ignoring auto-assign-floating-ip({}) in VM creation because not supported for cloud/type ({})", v, t);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/7582ebeb/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/AutoCreateFloatingIpsOption.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/AutoCreateFloatingIpsOption.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/AutoCreateFloatingIpsOption.java
new file mode 100644
index 0000000..4f91a8e
--- /dev/null
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/AutoCreateFloatingIpsOption.java
@@ -0,0 +1,41 @@
+/*
+ * 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.location.jclouds.templates.customize;
+
+import org.apache.brooklyn.location.jclouds.JcloudsLocationConfig;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.jclouds.compute.options.TemplateOptions;
+import org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class AutoCreateFloatingIpsOption implements TemplateOptionCustomizer {
+
+    private static final Logger LOG = LoggerFactory.getLogger(AutoCreateFloatingIpsOption.class);
+    
+    public void apply(TemplateOptions t, ConfigBag props, Object v) {
+        LOG.warn("Using deprecated " + JcloudsLocationConfig.AUTO_CREATE_FLOATING_IPS + "; use " + JcloudsLocationConfig.AUTO_ASSIGN_FLOATING_IP + " instead");
+        if (t instanceof NovaTemplateOptions) {
+            ((NovaTemplateOptions) t).autoAssignFloatingIp((Boolean) v);
+        } else {
+            LOG.info("ignoring auto-generate-floating-ips({}) in VM creation because not supported for cloud/type ({})", v, t);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/7582ebeb/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/AutoGenerateKeypairsOption.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/AutoGenerateKeypairsOption.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/AutoGenerateKeypairsOption.java
new file mode 100644
index 0000000..8d1601f
--- /dev/null
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/AutoGenerateKeypairsOption.java
@@ -0,0 +1,41 @@
+/*
+ * 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.location.jclouds.templates.customize;
+
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.jclouds.cloudstack.compute.options.CloudStackTemplateOptions;
+import org.jclouds.compute.options.TemplateOptions;
+import org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class AutoGenerateKeypairsOption implements TemplateOptionCustomizer {
+    private static final Logger LOG = LoggerFactory.getLogger(AutoGenerateKeypairsOption.class);
+
+    public void apply(TemplateOptions t, ConfigBag props, Object v) {
+        if (t instanceof NovaTemplateOptions) {
+            ((NovaTemplateOptions) t).generateKeyPair((Boolean) v);
+        } else if (t instanceof CloudStackTemplateOptions) {
+            ((CloudStackTemplateOptions) t).generateKeyPair((Boolean) v);
+        } else {
+            LOG.info("ignoring auto-generate-keypairs({}) in VM creation because not supported for cloud/type ({})", v, t);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/7582ebeb/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/DomainNameOption.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/DomainNameOption.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/DomainNameOption.java
new file mode 100644
index 0000000..54f67f2
--- /dev/null
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/DomainNameOption.java
@@ -0,0 +1,39 @@
+/*
+ * 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.location.jclouds.templates.customize;
+
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.flags.TypeCoercions;
+import org.jclouds.compute.options.TemplateOptions;
+import org.jclouds.softlayer.compute.options.SoftLayerTemplateOptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class DomainNameOption implements TemplateOptionCustomizer {
+    private static final Logger LOG = LoggerFactory.getLogger(DomainNameOption.class);
+
+    public void apply(TemplateOptions t, ConfigBag props, Object v) {
+        if (t instanceof SoftLayerTemplateOptions) {
+            ((SoftLayerTemplateOptions) t).domainName(TypeCoercions.coerce(v, String.class));
+        } else {
+            LOG.info("ignoring domain-name({}) in VM creation because not supported for cloud/type ({})", v, t);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/7582ebeb/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/ExtraPublicKeyDataToAuthOption.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/ExtraPublicKeyDataToAuthOption.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/ExtraPublicKeyDataToAuthOption.java
new file mode 100644
index 0000000..15ffd27
--- /dev/null
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/ExtraPublicKeyDataToAuthOption.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.location.jclouds.templates.customize;
+
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.jclouds.compute.options.TemplateOptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class ExtraPublicKeyDataToAuthOption implements TemplateOptionCustomizer {
+    private static final Logger LOG = LoggerFactory.getLogger(ExtraPublicKeyDataToAuthOption.class);
+
+    public void apply(TemplateOptions t, ConfigBag props, Object v) {
+        // this is unreliable:
+        // * seems now (Aug 2016) to be run *before* the TO.runScript which creates the user,
+        // so is installed for the initial login user not the created user
+        // * not supported in GCE (it uses it as the login public key, see email to jclouds list, 29 Aug 2015)
+        // so only works if you also overrideLoginPrivateKey
+        // --
+        // for this reason we also inspect these ourselves
+        // along with EXTRA_PUBLIC_KEY_URLS_TO_AUTH
+        // and install after creation;
+        // --
+        // we also do it here for legacy reasons though i (alex) can't think of any situations it's needed
+        // --
+        // also we warn on exceptions in case someone is dumping comments or something else
+        try {
+            t.authorizePublicKey(v.toString());
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            LOG.warn("Error trying jclouds authorizePublicKey; will run later: " + e, e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/7582ebeb/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/InboundPortsOption.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/InboundPortsOption.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/InboundPortsOption.java
new file mode 100644
index 0000000..8d233bc
--- /dev/null
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/InboundPortsOption.java
@@ -0,0 +1,49 @@
+/*
+ * 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.location.jclouds.templates.customize;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.apache.brooklyn.api.location.PortRange;
+import org.apache.brooklyn.core.location.PortRanges;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.commons.lang3.ArrayUtils;
+import org.jclouds.compute.options.TemplateOptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.Iterables;
+
+class InboundPortsOption implements TemplateOptionCustomizer {
+    private static final Logger LOG = LoggerFactory.getLogger(InboundPortsOption.class);
+
+    public void apply(TemplateOptions t, ConfigBag props, Object v) {
+        int[] inboundPorts = toIntPortArray(v);
+        if (LOG.isDebugEnabled())
+            LOG.debug("opening inbound ports {} for cloud/type {}", Arrays.toString(inboundPorts), t.getClass());
+        t.inboundPorts(inboundPorts);
+    }
+
+    private int[] toIntPortArray(Object v) {
+        PortRange portRange = PortRanges.fromIterable(Collections.singletonList(v));
+        return ArrayUtils.toPrimitive(Iterables.toArray(portRange, Integer.class));
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/7582ebeb/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/KeyPairOption.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/KeyPairOption.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/KeyPairOption.java
new file mode 100644
index 0000000..1edeaf2
--- /dev/null
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/KeyPairOption.java
@@ -0,0 +1,44 @@
+/*
+ * 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.location.jclouds.templates.customize;
+
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.jclouds.cloudstack.compute.options.CloudStackTemplateOptions;
+import org.jclouds.compute.options.TemplateOptions;
+import org.jclouds.ec2.compute.options.EC2TemplateOptions;
+import org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class KeyPairOption implements TemplateOptionCustomizer {
+    private static final Logger LOG = LoggerFactory.getLogger(KeyPairOption.class);
+
+    public void apply(TemplateOptions t, ConfigBag props, Object v) {
+        if (t instanceof EC2TemplateOptions) {
+            ((EC2TemplateOptions) t).keyPair(v.toString());
+        } else if (t instanceof NovaTemplateOptions) {
+            ((NovaTemplateOptions) t).keyPairName(v.toString());
+        } else if (t instanceof CloudStackTemplateOptions) {
+            ((CloudStackTemplateOptions) t).keyPair(v.toString());
+        } else {
+            LOG.info("ignoring keyPair({}) in VM creation because not supported for cloud/type ({})", v, t);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/7582ebeb/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/LoginUserOption.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/LoginUserOption.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/LoginUserOption.java
new file mode 100644
index 0000000..695f544
--- /dev/null
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/LoginUserOption.java
@@ -0,0 +1,31 @@
+/*
+ * 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.location.jclouds.templates.customize;
+
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.jclouds.compute.options.TemplateOptions;
+
+class LoginUserOption implements TemplateOptionCustomizer {
+    public void apply(TemplateOptions t, ConfigBag props, Object v) {
+        if (v != null) {
+            t.overrideLoginUser(v.toString());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/7582ebeb/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/LoginUserPasswordOption.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/LoginUserPasswordOption.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/LoginUserPasswordOption.java
new file mode 100644
index 0000000..9ee2b11
--- /dev/null
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/LoginUserPasswordOption.java
@@ -0,0 +1,31 @@
+/*
+ * 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.location.jclouds.templates.customize;
+
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.jclouds.compute.options.TemplateOptions;
+
+class LoginUserPasswordOption implements TemplateOptionCustomizer {
+    public void apply(TemplateOptions t, ConfigBag props, Object v) {
+        if (v != null) {
+            t.overrideLoginPassword(v.toString());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/7582ebeb/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/LoginUserPrivateKeyDataOption.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/LoginUserPrivateKeyDataOption.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/LoginUserPrivateKeyDataOption.java
new file mode 100644
index 0000000..403ca86
--- /dev/null
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/LoginUserPrivateKeyDataOption.java
@@ -0,0 +1,31 @@
+/*
+ * 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.location.jclouds.templates.customize;
+
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.jclouds.compute.options.TemplateOptions;
+
+class LoginUserPrivateKeyDataOption implements TemplateOptionCustomizer {
+    public void apply(TemplateOptions t, ConfigBag props, Object v) {
+        if (v != null) {
+            t.overrideLoginPrivateKey(v.toString());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/7582ebeb/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/LoginUserPrivateKeyFileOption.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/LoginUserPrivateKeyFileOption.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/LoginUserPrivateKeyFileOption.java
new file mode 100644
index 0000000..b18ef7f
--- /dev/null
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/LoginUserPrivateKeyFileOption.java
@@ -0,0 +1,51 @@
+/*
+ * 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.location.jclouds.templates.customize;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.os.Os;
+import org.jclouds.compute.options.TemplateOptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+
+class LoginUserPrivateKeyFileOption implements TemplateOptionCustomizer {
+    private static final Logger LOG = LoggerFactory.getLogger(LoginUserPrivateKeyFileOption.class);
+
+    public void apply(TemplateOptions t, ConfigBag props, Object v) {
+        if (v != null) {
+            String privateKeyFileName = v.toString();
+            String privateKey;
+            try {
+                privateKey = Files.toString(new File(Os.tidyPath(privateKeyFileName)), Charsets.UTF_8);
+            } catch (IOException e) {
+                LOG.error(privateKeyFileName + "not found", e);
+                throw Exceptions.propagate(e);
+            }
+            t.overrideLoginPrivateKey(privateKey);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/7582ebeb/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/NetworkNameOption.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/NetworkNameOption.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/NetworkNameOption.java
new file mode 100644
index 0000000..e4bf045
--- /dev/null
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/NetworkNameOption.java
@@ -0,0 +1,65 @@
+/*
+ * 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.location.jclouds.templates.customize;
+
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.jclouds.aws.ec2.compute.AWSEC2TemplateOptions;
+import org.jclouds.cloudstack.compute.options.CloudStackTemplateOptions;
+import org.jclouds.compute.options.TemplateOptions;
+import org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOptions;
+import org.jclouds.softlayer.compute.options.SoftLayerTemplateOptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class NetworkNameOption implements TemplateOptionCustomizer {
+    private static final Logger LOG = LoggerFactory.getLogger(NetworkNameOption.class);
+
+    public void apply(TemplateOptions t, ConfigBag props, Object v) {
+        if (t instanceof AWSEC2TemplateOptions) {
+            // subnet ID is the sensible interpretation of network name in EC2
+            ((AWSEC2TemplateOptions) t).subnetId((String) v);
+
+        } else {
+            if (isGoogleComputeTemplateOptions(t)) {
+                // no warning needed
+                // we think this is the only jclouds endpoint which supports this option
+
+            } else if (t instanceof SoftLayerTemplateOptions) {
+                LOG.warn("networkName is not be supported in SoftLayer; use `templateOptions` with `primaryNetworkComponentNetworkVlanId` or `primaryNetworkBackendComponentNetworkVlanId`");
+            } else if (!(t instanceof CloudStackTemplateOptions) && !(t instanceof NovaTemplateOptions)) {
+                LOG.warn("networkName is experimental in many jclouds endpoints may not be supported in this cloud");
+                // NB, from @andreaturli
+//                                Cloudstack uses custom securityGroupIds and networkIds not the generic networks
+//                                Openstack Nova uses securityGroupNames which is marked as @deprecated (suggests to use groups which is maybe even more confusing)
+//                                Azure supports the custom networkSecurityGroupName
+            }
+
+            t.networks((String) v);
+        }
+    }
+
+    /**
+     * Avoid having a dependency on googlecompute because it doesn't have an OSGi bundle yet.
+     * Fixed in jclouds 2.0.0-SNAPSHOT
+     */
+    private static boolean isGoogleComputeTemplateOptions(TemplateOptions t) {
+        return t.getClass().getName().equals("org.jclouds.googlecomputeengine.compute.options.GoogleComputeEngineTemplateOptions");
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/7582ebeb/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/RunAsRootOption.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/RunAsRootOption.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/RunAsRootOption.java
new file mode 100644
index 0000000..326b883
--- /dev/null
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/RunAsRootOption.java
@@ -0,0 +1,29 @@
+/*
+ * 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.location.jclouds.templates.customize;
+
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.jclouds.compute.options.TemplateOptions;
+
+class RunAsRootOption implements TemplateOptionCustomizer {
+    public void apply(TemplateOptions t, ConfigBag props, Object v) {
+        t.runAsRoot((Boolean)v);
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/7582ebeb/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/SecurityGroupOption.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/SecurityGroupOption.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/SecurityGroupOption.java
new file mode 100644
index 0000000..ee1040d
--- /dev/null
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/SecurityGroupOption.java
@@ -0,0 +1,63 @@
+/*
+ * 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.location.jclouds.templates.customize;
+
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.text.Strings;
+import org.jclouds.compute.options.TemplateOptions;
+import org.jclouds.ec2.compute.options.EC2TemplateOptions;
+import org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOptions;
+import org.jclouds.softlayer.compute.options.SoftLayerTemplateOptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class SecurityGroupOption implements TemplateOptionCustomizer {
+    private static final Logger LOG = LoggerFactory.getLogger(SecurityGroupOption.class);
+
+    public void apply(TemplateOptions t, ConfigBag props, Object v) {
+        if (t instanceof EC2TemplateOptions) {
+            String[] securityGroups = toStringArray(v);
+            ((EC2TemplateOptions) t).securityGroups(securityGroups);
+        } else if (t instanceof NovaTemplateOptions) {
+            String[] securityGroups = toStringArray(v);
+            t.securityGroups(securityGroups);
+        } else if (t instanceof SoftLayerTemplateOptions) {
+            String[] securityGroups = toStringArray(v);
+            t.securityGroups(securityGroups);
+        } else if (isGoogleComputeTemplateOptions(t)) {
+            String[] securityGroups = toStringArray(v);
+            t.securityGroups(securityGroups);
+        } else {
+            LOG.info("ignoring securityGroups({}) in VM creation because not supported for cloud/type ({})", v, t.getClass());
+        }
+    }
+
+    private String[] toStringArray(Object v) {
+        return Strings.toStringList(v).toArray(new String[0]);
+    }
+
+    /**
+     * Avoid having a dependency on googlecompute because it doesn't have an OSGi bundle yet.
+     * Fixed in jclouds 2.0.0-SNAPSHOT
+     */
+    private static boolean isGoogleComputeTemplateOptions(TemplateOptions t) {
+        return t.getClass().getName().equals("org.jclouds.googlecomputeengine.compute.options.GoogleComputeEngineTemplateOptions");
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/7582ebeb/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/StringTagsOption.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/StringTagsOption.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/StringTagsOption.java
new file mode 100644
index 0000000..153577c
--- /dev/null
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/StringTagsOption.java
@@ -0,0 +1,40 @@
+/*
+ * 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.location.jclouds.templates.customize;
+
+import java.util.List;
+
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.text.Strings;
+import org.jclouds.compute.options.TemplateOptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class StringTagsOption implements TemplateOptionCustomizer {
+    private static final Logger LOG = LoggerFactory.getLogger(StringTagsOption.class);
+
+    public void apply(TemplateOptions t, ConfigBag props, Object v) {
+        List<String> tags = Strings.toStringList(v);
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("setting VM tags {} for {}", tags, t);
+        }
+        t.tags(tags);
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/7582ebeb/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/TemplateOptionCustomizer.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/TemplateOptionCustomizer.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/TemplateOptionCustomizer.java
new file mode 100644
index 0000000..99346a7
--- /dev/null
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/TemplateOptionCustomizer.java
@@ -0,0 +1,29 @@
+/*
+ * 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.location.jclouds.templates.customize;
+
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.jclouds.compute.options.TemplateOptions;
+
+public interface TemplateOptionCustomizer {
+
+    void apply(TemplateOptions tb, ConfigBag props, Object v);
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/7582ebeb/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/TemplateOptionCustomizers.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/TemplateOptionCustomizers.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/TemplateOptionCustomizers.java
new file mode 100644
index 0000000..3cc7807
--- /dev/null
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/TemplateOptionCustomizers.java
@@ -0,0 +1,103 @@
+/*
+ * 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.location.jclouds.templates.customize;
+
+// Has "2" in its name so JcloudsLocation can use it without having to use a fully qualified reference.
+public class TemplateOptionCustomizers {
+
+    private TemplateOptionCustomizers() {}
+
+    public static TemplateOptionCustomizer autoAssignFloatingIp() {
+        return new AutoAssignFloatingIpOption();
+    }
+
+    public static TemplateOptionCustomizer autoCreateFloatingIps() {
+        return new AutoCreateFloatingIpsOption();
+    }
+
+    public static TemplateOptionCustomizer autoGenerateKeypairs() {
+        return new AutoGenerateKeypairsOption();
+    }
+
+    public static TemplateOptionCustomizer domainName() {
+        return new DomainNameOption();
+    }
+
+    public static TemplateOptionCustomizer extraPublicKeyDataToAuth() {
+        return new ExtraPublicKeyDataToAuthOption();
+    }
+
+    public static TemplateOptionCustomizer inboundPorts() {
+        return new InboundPortsOption();
+    }
+
+    public static TemplateOptionCustomizer keyPair() {
+        return new KeyPairOption();
+    }
+
+    public static TemplateOptionCustomizer loginUser() {
+        return new LoginUserOption();
+    }
+
+    public static TemplateOptionCustomizer loginUserPassword() {
+        return new LoginUserPasswordOption();
+    }
+
+    public static TemplateOptionCustomizer loginUserPrivateKeyData() {
+        return new LoginUserPrivateKeyDataOption();
+    }
+
+    public static TemplateOptionCustomizer loginUserPrivateKeyFile() {
+        return new LoginUserPrivateKeyFileOption();
+    }
+
+    public static TemplateOptionCustomizer networkName() {
+        return new NetworkNameOption();
+    }
+
+    public static TemplateOptionCustomizer runAsRoot() {
+        return new RunAsRootOption();
+    }
+
+    public static TemplateOptionCustomizer securityGroups() {
+        return new SecurityGroupOption();
+    }
+
+    public static TemplateOptionCustomizer stringTags() {
+        return new StringTagsOption();
+    }
+
+    public static TemplateOptionCustomizer templateOptions() {
+        return new TemplateOptionsOption();
+    }
+
+    public static TemplateOptionCustomizer userDataUuencoded() {
+        return new UserDataUuencodedOption();
+    }
+
+    public static TemplateOptionCustomizer userMetadataMap() {
+        return new UserMetadataMapOption();
+    }
+
+    public static TemplateOptionCustomizer userMetadataString() {
+        return new UserMetadataStringOption();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/7582ebeb/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/TemplateOptionsOption.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/TemplateOptionsOption.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/TemplateOptionsOption.java
new file mode 100644
index 0000000..cd26535
--- /dev/null
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/TemplateOptionsOption.java
@@ -0,0 +1,55 @@
+/*
+ * 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.location.jclouds.templates.customize;
+
+import java.util.Map;
+
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.flags.MethodCoercions;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.jclouds.compute.options.TemplateOptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class TemplateOptionsOption implements TemplateOptionCustomizer {
+    private static final Logger LOG = LoggerFactory.getLogger(TemplateOptionsOption.class);
+
+    @Override
+    public void apply(TemplateOptions options, ConfigBag config, Object v) {
+        if (v == null) return;
+        @SuppressWarnings("unchecked") Map<String, Object> optionsMap = (Map<String, Object>) v;
+        if (optionsMap.isEmpty()) return;
+
+        Class<? extends TemplateOptions> clazz = options.getClass();
+        for (final Map.Entry<String, Object> option : optionsMap.entrySet()) {
+            if (option.getValue() != null) {
+                Maybe<?> result = MethodCoercions.tryFindAndInvokeBestMatchingMethod(options, option.getKey(), option.getValue());
+                if (result.isAbsent()) {
+                    LOG.warn("Ignoring request to set template option {} because this is not supported by {}", new Object[]{option.getKey(), clazz.getCanonicalName()});
+                }
+            } else {
+                // jclouds really doesn't like you to pass nulls; don't do it! For us,
+                // null is the only way to remove an inherited value when the templateOptions
+                // map is being merged.
+                LOG.debug("Ignoring request to set template option {} because value is null", new Object[]{option.getKey(), clazz.getCanonicalName()});
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/7582ebeb/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/UserDataUuencodedOption.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/UserDataUuencodedOption.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/UserDataUuencodedOption.java
new file mode 100644
index 0000000..18fdb38
--- /dev/null
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/UserDataUuencodedOption.java
@@ -0,0 +1,53 @@
+/*
+ * 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.location.jclouds.templates.customize;
+
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.text.Strings;
+import org.jclouds.compute.options.TemplateOptions;
+import org.jclouds.ec2.compute.options.EC2TemplateOptions;
+import org.jclouds.softlayer.compute.options.SoftLayerTemplateOptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class UserDataUuencodedOption implements TemplateOptionCustomizer {
+    private static final Logger LOG = LoggerFactory.getLogger(UserDataUuencodedOption.class);
+
+    public void apply(TemplateOptions t, ConfigBag props, Object v) {
+        if (t instanceof EC2TemplateOptions) {
+            byte[] bytes = toByteArray(v);
+            ((EC2TemplateOptions) t).userData(bytes);
+        } else if (t instanceof SoftLayerTemplateOptions) {
+            ((SoftLayerTemplateOptions) t).userData(Strings.toString(v));
+        } else {
+            LOG.info("ignoring userData({}) in VM creation because not supported for cloud/type ({})", v, t.getClass());
+        }
+    }
+
+    private byte[] toByteArray(Object v) {
+        if (v instanceof byte[]) {
+            return (byte[]) v;
+        } else if (v instanceof CharSequence) {
+            return v.toString().getBytes();
+        } else {
+            throw new IllegalArgumentException("Invalid type for byte[]: " + v + " of type " + v.getClass());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/7582ebeb/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/UserMetadataMapOption.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/UserMetadataMapOption.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/UserMetadataMapOption.java
new file mode 100644
index 0000000..8650e9b
--- /dev/null
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/UserMetadataMapOption.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.location.jclouds.templates.customize;
+
+import java.util.Map;
+
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.text.KeyValueParser;
+import org.jclouds.compute.options.TemplateOptions;
+
+import com.google.common.collect.Maps;
+
+class UserMetadataMapOption implements TemplateOptionCustomizer {
+    public void apply(TemplateOptions t, ConfigBag props, Object v) {
+        if (v != null) {
+            t.userMetadata(toMapStringString(v));
+        }
+    }
+    private Map<String,String> toMapStringString(Object v) {
+        if (v instanceof Map<?,?>) {
+            Map<String,String> result = Maps.newLinkedHashMap();
+            for (Map.Entry<?,?> entry : ((Map<?,?>)v).entrySet()) {
+                String key = entry.getKey().toString();
+                String value = entry.getValue().toString();
+                result.put(key, value);
+            }
+            return result;
+        } else if (v instanceof CharSequence) {
+            return KeyValueParser.parseMap(v.toString());
+        } else {
+            throw new IllegalArgumentException("Invalid type for Map<String,String>: " + v +
+                    (v != null ? " of type "+v.getClass() : ""));
+        }
+    }
+}


[6/7] brooklyn-server git commit: Extract CustomizeTemplateBuilder from JcloudsLocation

Posted by sj...@apache.org.
Extract CustomizeTemplateBuilder from JcloudsLocation


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/f89ba453
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/f89ba453
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/f89ba453

Branch: refs/heads/master
Commit: f89ba453253cadf2d0df1bf05d741543d8e36a9b
Parents: 7582ebe
Author: Sam Corbett <sa...@cloudsoftcorp.com>
Authored: Fri Dec 9 17:29:55 2016 +0000
Committer: Sam Corbett <sa...@cloudsoftcorp.com>
Committed: Tue Dec 20 15:40:12 2016 +0000

----------------------------------------------------------------------
 .../location/jclouds/JcloudsLocation.java       |  88 +++-------
 .../customize/TemplateBuilderCustomizer.java    |  29 ++++
 .../customize/TemplateBuilderCustomizers.java   | 164 +++++++++++++++++++
 3 files changed, 213 insertions(+), 68 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/f89ba453/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
index fd06a9a..67aa826 100644
--- a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
@@ -78,6 +78,8 @@ import org.apache.brooklyn.core.mgmt.persist.PersistenceObjectStore;
 import org.apache.brooklyn.core.mgmt.persist.jclouds.JcloudsBlobStoreBasedObjectStore;
 import org.apache.brooklyn.location.jclouds.networking.JcloudsPortForwarderExtension;
 import org.apache.brooklyn.location.jclouds.templates.PortableTemplateBuilder;
+import org.apache.brooklyn.location.jclouds.templates.customize.TemplateBuilderCustomizer;
+import org.apache.brooklyn.location.jclouds.templates.customize.TemplateBuilderCustomizers;
 import org.apache.brooklyn.location.jclouds.templates.customize.TemplateOptionCustomizer;
 import org.apache.brooklyn.location.jclouds.templates.customize.TemplateOptionCustomizers;
 import org.apache.brooklyn.location.jclouds.zone.AwsAvailabilityZoneExtension;
@@ -92,7 +94,6 @@ import org.apache.brooklyn.util.core.ResourceUtils;
 import org.apache.brooklyn.util.core.config.ConfigBag;
 import org.apache.brooklyn.util.core.config.ResolvingConfigBag;
 import org.apache.brooklyn.util.core.flags.SetFromFlag;
-import org.apache.brooklyn.util.core.flags.TypeCoercions;
 import org.apache.brooklyn.util.core.internal.ssh.ShellTool;
 import org.apache.brooklyn.util.core.internal.ssh.SshTool;
 import org.apache.brooklyn.util.core.internal.winrm.WinRmTool;
@@ -109,7 +110,6 @@ import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.exceptions.ReferenceWithError;
 import org.apache.brooklyn.util.exceptions.UserFacingException;
 import org.apache.brooklyn.util.guava.Maybe;
-import org.apache.brooklyn.util.javalang.Enums;
 import org.apache.brooklyn.util.javalang.Reflections;
 import org.apache.brooklyn.util.net.Cidr;
 import org.apache.brooklyn.util.net.Networking;
@@ -120,7 +120,6 @@ import org.apache.brooklyn.util.ssh.IptablesCommands;
 import org.apache.brooklyn.util.ssh.IptablesCommands.Chain;
 import org.apache.brooklyn.util.ssh.IptablesCommands.Policy;
 import org.apache.brooklyn.util.stream.Streams;
-import org.apache.brooklyn.util.text.ByteSizeStrings;
 import org.apache.brooklyn.util.text.KeyValueParser;
 import org.apache.brooklyn.util.text.StringPredicates;
 import org.apache.brooklyn.util.text.Strings;
@@ -141,7 +140,6 @@ import org.jclouds.compute.domain.OperatingSystem;
 import org.jclouds.compute.domain.OsFamily;
 import org.jclouds.compute.domain.Template;
 import org.jclouds.compute.domain.TemplateBuilder;
-import org.jclouds.compute.domain.TemplateBuilderSpec;
 import org.jclouds.compute.options.TemplateOptions;
 import org.jclouds.domain.Credentials;
 import org.jclouds.domain.LocationScope;
@@ -1232,74 +1230,27 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
 
     // ------------- constructing the template, etc ------------------------
 
-    private interface CustomizeTemplateBuilder {
-        void apply(TemplateBuilder tb, ConfigBag props, Object v);
-    }
-
     /** @deprecated since 0.11.0 use {@link TemplateOptionCustomizer} instead */
     @Deprecated
     public interface CustomizeTemplateOptions extends TemplateOptionCustomizer {
     }
 
     /** properties which cause customization of the TemplateBuilder */
-    public static final Map<ConfigKey<?>, CustomizeTemplateBuilder> SUPPORTED_TEMPLATE_BUILDER_PROPERTIES = ImmutableMap.<ConfigKey<?>,CustomizeTemplateBuilder>builder()
-            .put(OS_64_BIT, new CustomizeTemplateBuilder() {
-                    public void apply(TemplateBuilder tb, ConfigBag props, Object v) {
-                        Boolean os64Bit = TypeCoercions.coerce(v, Boolean.class);
-                        if (os64Bit!=null)
-                            tb.os64Bit(os64Bit);
-                    }})
-            .put(MIN_RAM, new CustomizeTemplateBuilder() {
-                    public void apply(TemplateBuilder tb, ConfigBag props, Object v) {
-                        tb.minRam( (int)(ByteSizeStrings.parse(Strings.toString(v), "mb")/1000/1000) );
-                    }})
-            .put(MIN_CORES, new CustomizeTemplateBuilder() {
-                    public void apply(TemplateBuilder tb, ConfigBag props, Object v) {
-                        tb.minCores(TypeCoercions.coerce(v, Double.class));
-                    }})
-            .put(MIN_DISK, new CustomizeTemplateBuilder() {
-                    public void apply(TemplateBuilder tb, ConfigBag props, Object v) {
-                        tb.minDisk( (int)(ByteSizeStrings.parse(Strings.toString(v), "gb")/1000/1000/1000) );
-                    }})
-            .put(HARDWARE_ID, new CustomizeTemplateBuilder() {
-                    public void apply(TemplateBuilder tb, ConfigBag props, Object v) {
-                        tb.hardwareId(((CharSequence)v).toString());
-                    }})
-            .put(IMAGE_ID, new CustomizeTemplateBuilder() {
-                    public void apply(TemplateBuilder tb, ConfigBag props, Object v) {
-                        tb.imageId(((CharSequence)v).toString());
-                    }})
-            .put(IMAGE_DESCRIPTION_REGEX, new CustomizeTemplateBuilder() {
-                    public void apply(TemplateBuilder tb, ConfigBag props, Object v) {
-                        tb.imageDescriptionMatches(((CharSequence)v).toString());
-                    }})
-            .put(IMAGE_NAME_REGEX, new CustomizeTemplateBuilder() {
-                    public void apply(TemplateBuilder tb, ConfigBag props, Object v) {
-                        tb.imageNameMatches(((CharSequence)v).toString());
-                    }})
-            .put(OS_FAMILY, new CustomizeTemplateBuilder() {
-                    public void apply(TemplateBuilder tb, ConfigBag props, Object v) {
-                        Maybe<OsFamily> osFamily = Enums.valueOfIgnoreCase(OsFamily.class, v.toString());
-                        if (osFamily.isAbsent())
-                            throw new IllegalArgumentException("Invalid "+OS_FAMILY+" value "+v);
-                        tb.osFamily(osFamily.get());
-                    }})
-            .put(OS_VERSION_REGEX, new CustomizeTemplateBuilder() {
-                    public void apply(TemplateBuilder tb, ConfigBag props, Object v) {
-                        tb.osVersionMatches( ((CharSequence)v).toString() );
-                    }})
-            .put(TEMPLATE_SPEC, new CustomizeTemplateBuilder() {
-                public void apply(TemplateBuilder tb, ConfigBag props, Object v) {
-                        tb.from(TemplateBuilderSpec.parse(((CharSequence)v).toString()));
-                    }})
-            .put(DEFAULT_IMAGE_ID, new CustomizeTemplateBuilder() {
-                    public void apply(TemplateBuilder tb, ConfigBag props, Object v) {
-                        /* done in the code, but included here so that it is in the map */
-                    }})
-            .put(TEMPLATE_BUILDER, new CustomizeTemplateBuilder() {
-                    public void apply(TemplateBuilder tb, ConfigBag props, Object v) {
-                        /* done in the code, but included here so that it is in the map */
-                    }})
+    public static final Map<ConfigKey<?>, ? extends TemplateBuilderCustomizer> SUPPORTED_TEMPLATE_BUILDER_PROPERTIES = ImmutableMap.<ConfigKey<?>, TemplateBuilderCustomizer>builder()
+            .put(HARDWARE_ID, TemplateBuilderCustomizers.hardwareId())
+            .put(IMAGE_DESCRIPTION_REGEX, TemplateBuilderCustomizers.imageDescription())
+            .put(IMAGE_ID, TemplateBuilderCustomizers.imageId())
+            .put(IMAGE_NAME_REGEX, TemplateBuilderCustomizers.imageNameRegex())
+            .put(MIN_CORES, TemplateBuilderCustomizers.minCores())
+            .put(MIN_DISK, TemplateBuilderCustomizers.minDisk())
+            .put(MIN_RAM, TemplateBuilderCustomizers.minRam())
+            .put(OS_64_BIT, TemplateBuilderCustomizers.os64Bit())
+            .put(OS_FAMILY, TemplateBuilderCustomizers.osFamily())
+            .put(OS_VERSION_REGEX, TemplateBuilderCustomizers.osVersionRegex())
+            .put(TEMPLATE_SPEC, TemplateBuilderCustomizers.templateSpec())
+            /* Both done in the code, but included here so that they are in the map */
+            .put(DEFAULT_IMAGE_ID, TemplateBuilderCustomizers.noOp())
+            .put(TEMPLATE_BUILDER, TemplateBuilderCustomizers.noOp())
             .build();
 
     /** properties which cause customization of the TemplateOptions */
@@ -1419,11 +1370,11 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
         }
 
         // Apply the template builder and options properties
-        for (Map.Entry<ConfigKey<?>, CustomizeTemplateBuilder> entry : SUPPORTED_TEMPLATE_BUILDER_PROPERTIES.entrySet()) {
+        for (Map.Entry<ConfigKey<?>, ? extends TemplateBuilderCustomizer> entry : SUPPORTED_TEMPLATE_BUILDER_PROPERTIES.entrySet()) {
             ConfigKey<?> key = entry.getKey();
             Object val = config.containsKey(key) ? config.get(key) : key.getDefaultValue();
             if (val != null) {
-                CustomizeTemplateBuilder code = entry.getValue();
+                TemplateBuilderCustomizer code = entry.getValue();
                 code.apply(templateBuilder, config, val);
             }
         }
@@ -3100,4 +3051,5 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
             return val1;
         }
     }
+
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/f89ba453/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/TemplateBuilderCustomizer.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/TemplateBuilderCustomizer.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/TemplateBuilderCustomizer.java
new file mode 100644
index 0000000..3c6e237
--- /dev/null
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/TemplateBuilderCustomizer.java
@@ -0,0 +1,29 @@
+/*
+ * 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.location.jclouds.templates.customize;
+
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.jclouds.compute.domain.TemplateBuilder;
+
+public interface TemplateBuilderCustomizer {
+
+    void apply(TemplateBuilder tb, ConfigBag props, Object v);
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/f89ba453/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/TemplateBuilderCustomizers.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/TemplateBuilderCustomizers.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/TemplateBuilderCustomizers.java
new file mode 100644
index 0000000..e4426d6
--- /dev/null
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/templates/customize/TemplateBuilderCustomizers.java
@@ -0,0 +1,164 @@
+/*
+ * 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.location.jclouds.templates.customize;
+
+import org.apache.brooklyn.location.jclouds.JcloudsLocationConfig;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.flags.TypeCoercions;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.javalang.Enums;
+import org.apache.brooklyn.util.text.ByteSizeStrings;
+import org.apache.brooklyn.util.text.Strings;
+import org.jclouds.compute.domain.OsFamily;
+import org.jclouds.compute.domain.TemplateBuilder;
+import org.jclouds.compute.domain.TemplateBuilderSpec;
+
+public class TemplateBuilderCustomizers {
+
+    private TemplateBuilderCustomizers() {
+    }
+
+    public static TemplateBuilderCustomizer hardwareId() {
+        return new HardwareIdTemplateBuilder();
+    }
+
+    public static TemplateBuilderCustomizer imageDescription() {
+        return new ImageDescriptionRegexTemplateBuilder();
+    }
+
+    public static TemplateBuilderCustomizer imageId() {
+        return new ImageIdTemplateBuilder();
+    }
+
+    public static TemplateBuilderCustomizer imageNameRegex() {
+        return new ImageNameRegexTemplateBuilder();
+    }
+
+    public static TemplateBuilderCustomizer minCores() {
+        return new MinCoresTemplateBuilder();
+    }
+
+    public static TemplateBuilderCustomizer minDisk() {
+        return new MinDiskTemplateBuilder();
+    }
+
+    public static TemplateBuilderCustomizer minRam() {
+        return new MinRamTemplateBuilder();
+    }
+
+    public static TemplateBuilderCustomizer noOp() {
+        return new NoOpTemplateBuilder();
+    }
+
+    public static TemplateBuilderCustomizer os64Bit() {
+        return new Os64BitTemplateBuidler();
+    }
+
+    public static TemplateBuilderCustomizer osFamily() {
+        return new OsFamilyTemplateBuilder();
+    }
+
+    public static TemplateBuilderCustomizer osVersionRegex() {
+        return new OsVersionRegexTemplateBuilder();
+    }
+
+    public static TemplateBuilderCustomizer templateSpec() {
+        return new TemplateSpecTemplateBuilder();
+    }
+
+    private static class MinRamTemplateBuilder implements TemplateBuilderCustomizer {
+        public void apply(TemplateBuilder tb, ConfigBag props, Object v) {
+            tb.minRam((int) (ByteSizeStrings.parse(Strings.toString(v), "mb") / 1000 / 1000));
+        }
+    }
+
+    private static class MinCoresTemplateBuilder implements TemplateBuilderCustomizer {
+        public void apply(TemplateBuilder tb, ConfigBag props, Object v) {
+            tb.minCores(TypeCoercions.coerce(v, Double.class));
+        }
+    }
+
+    private static class MinDiskTemplateBuilder implements TemplateBuilderCustomizer {
+        public void apply(TemplateBuilder tb, ConfigBag props, Object v) {
+            tb.minDisk((int) (ByteSizeStrings.parse(Strings.toString(v), "gb") / 1000 / 1000 / 1000));
+        }
+    }
+
+    private static class HardwareIdTemplateBuilder implements TemplateBuilderCustomizer {
+        public void apply(TemplateBuilder tb, ConfigBag props, Object v) {
+            tb.hardwareId(v.toString());
+        }
+    }
+
+    private static class ImageIdTemplateBuilder implements TemplateBuilderCustomizer {
+        public void apply(TemplateBuilder tb, ConfigBag props, Object v) {
+            tb.imageId(v.toString());
+        }
+    }
+
+    private static class ImageDescriptionRegexTemplateBuilder implements TemplateBuilderCustomizer {
+        public void apply(TemplateBuilder tb, ConfigBag props, Object v) {
+            tb.imageDescriptionMatches(v.toString());
+        }
+    }
+
+    private static class ImageNameRegexTemplateBuilder implements TemplateBuilderCustomizer {
+        public void apply(TemplateBuilder tb, ConfigBag props, Object v) {
+            tb.imageNameMatches(v.toString());
+        }
+    }
+
+    private static class OsFamilyTemplateBuilder implements TemplateBuilderCustomizer {
+        public void apply(TemplateBuilder tb, ConfigBag props, Object v) {
+            Maybe<OsFamily> osFamily = Enums.valueOfIgnoreCase(OsFamily.class, v.toString());
+            if (osFamily.isAbsent()) {
+                throw new IllegalArgumentException("Invalid " + JcloudsLocationConfig.OS_FAMILY + " value " + v);
+            }
+            tb.osFamily(osFamily.get());
+        }
+    }
+
+    private static class OsVersionRegexTemplateBuilder implements TemplateBuilderCustomizer {
+        public void apply(TemplateBuilder tb, ConfigBag props, Object v) {
+            tb.osVersionMatches(v.toString());
+        }
+    }
+
+    private static class TemplateSpecTemplateBuilder implements TemplateBuilderCustomizer {
+        public void apply(TemplateBuilder tb, ConfigBag props, Object v) {
+            tb.from(TemplateBuilderSpec.parse(v.toString()));
+        }
+    }
+
+    private static class Os64BitTemplateBuidler implements TemplateBuilderCustomizer {
+        public void apply(TemplateBuilder tb, ConfigBag props, Object v) {
+            Boolean os64Bit = TypeCoercions.coerce(v, Boolean.class);
+            if (os64Bit != null) {
+                tb.os64Bit(os64Bit);
+            }
+        }
+    }
+
+    private static class NoOpTemplateBuilder implements TemplateBuilderCustomizer {
+        @Override
+        public void apply(TemplateBuilder tb, ConfigBag props, Object v) {
+        }
+    }
+}


[2/7] brooklyn-server git commit: Move JcloudsLocation's logic for creating users to separate class

Posted by sj...@apache.org.
Move JcloudsLocation's logic for creating users to separate class


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/c93a79de
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/c93a79de
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/c93a79de

Branch: refs/heads/master
Commit: c93a79de41d32616e5cec03d07632daefe0d28ca
Parents: 2cbe309
Author: Sam Corbett <sa...@cloudsoftcorp.com>
Authored: Wed Dec 7 11:56:40 2016 +0000
Committer: Sam Corbett <sa...@cloudsoftcorp.com>
Committed: Tue Dec 20 15:00:26 2016 +0000

----------------------------------------------------------------------
 .../location/jclouds/CreateUserStatements.java  | 303 ++++++++++++++
 .../location/jclouds/JcloudsLocation.java       | 405 ++++---------------
 .../location/jclouds/JcloudsLocationTest.java   |   6 +-
 3 files changed, 393 insertions(+), 321 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/c93a79de/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/CreateUserStatements.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/CreateUserStatements.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/CreateUserStatements.java
new file mode 100644
index 0000000..d275fc1
--- /dev/null
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/CreateUserStatements.java
@@ -0,0 +1,303 @@
+/*
+ * 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.location.jclouds;
+
+import static org.apache.brooklyn.util.JavaGroovyEquivalents.groovyTruth;
+
+import java.security.KeyPair;
+import java.util.List;
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.core.location.LocationConfigUtils;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.crypto.SecureKeys;
+import org.apache.brooklyn.util.text.Identifiers;
+import org.apache.brooklyn.util.text.Strings;
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.functions.Sha512Crypt;
+import org.jclouds.domain.LoginCredentials;
+import org.jclouds.scriptbuilder.domain.LiteralStatement;
+import org.jclouds.scriptbuilder.domain.Statement;
+import org.jclouds.scriptbuilder.statements.login.AdminAccess;
+import org.jclouds.scriptbuilder.statements.login.ReplaceShadowPasswordEntry;
+import org.jclouds.scriptbuilder.statements.ssh.AuthorizeRSAPublicKeys;
+import org.jclouds.scriptbuilder.statements.ssh.SshStatements;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+
+public class CreateUserStatements {
+
+    private static final Logger LOG = LoggerFactory.getLogger(CreateUserStatements.class);
+
+    private final LoginCredentials createdUserCredentials;
+    private final List<Statement> statements;
+
+    CreateUserStatements(LoginCredentials creds, List<Statement> statements) {
+        this.createdUserCredentials = creds;
+        this.statements = statements;
+    }
+
+    public LoginCredentials credentials() {
+        return createdUserCredentials;
+    }
+
+    public List<Statement> statements() {
+        return statements;
+    }
+
+    /**
+     * Returns the commands required to create the user, to be used for connecting (e.g. over ssh)
+     * to the machine; also returns the expected login credentials.
+     * <p>
+     * The returned login credentials may be null if we haven't done any user-setup and no specific
+     * user was supplied (i.e. if {@code dontCreateUser} was true and {@code user} was null or blank).
+     * In which case, the caller should use the jclouds node's login credentials.
+     * <p>
+     * There are quite a few configuration options. Depending on their values, the user-creation
+     * behaves differently:
+     * <ul>
+     * <li>{@code dontCreateUser} says not to run any user-setup commands at all. If {@code user} is
+     * non-empty (including with the default value), then that user will subsequently be used,
+     * otherwise the (inferred) {@code loginUser} will be used.
+     * <li>{@code loginUser} refers to the existing user that jclouds should use when setting up the VM.
+     * Normally this will be inferred from the image (i.e. doesn't need to be explicitly set), but sometimes
+     * the image gets it wrong so this can be a handy override.
+     * <li>{@code user} is the username for brooklyn to subsequently use when ssh'ing to the machine.
+     * If not explicitly set, its value will default to the username of the user running brooklyn.
+     * <ul>
+     * <li>If the {@code user} value is null or empty, then the (inferred) {@code loginUser} will
+     * subsequently be used, setting up the password/authorizedKeys for that loginUser.
+     * <li>If the {@code user} is "root", then setup the password/authorizedKeys for root.
+     * <li>If the {@code user} equals the (inferred) {@code loginUser}, then don't try to create this
+     * user but instead just setup the password/authorizedKeys for the user.
+     * <li>Otherwise create the given user, setting up the password/authorizedKeys (unless
+     * {@code dontCreateUser} is set, obviously).
+     * </ul>
+     * <li>{@code publicKeyData} is the key to authorize (i.e. add to .ssh/authorized_keys),
+     * if not null or blank. Note the default is to use {@code ~/.ssh/id_rsa.pub} or {@code ~/.ssh/id_dsa.pub}
+     * if either of those files exist for the user running brooklyn.
+     * Related is {@code publicKeyFile}, which is used to populate publicKeyData.
+     * <li>{@code password} is the password to set for the user. If null or blank, then a random password
+     * will be auto-generated and set.
+     * <li>{@code privateKeyData} is the key to use when subsequent ssh'ing, if not null or blank.
+     * Note the default is to use {@code ~/.ssh/id_rsa} or {@code ~/.ssh/id_dsa}.
+     * The subsequent preferences for ssh'ing are:
+     * <ul>
+     * <li>Use the {@code privateKeyData} if not null or blank (including if using default)
+     * <li>Use the {@code password} (or the auto-generated password if that is blank).
+     * </ul>
+     * <li>{@code grantUserSudo} determines whether or not the created user may run the sudo command.</li>
+     * </ul>
+     *
+     * @param image  The image being used to create the VM
+     * @param config Configuration for creating the VM
+     * @return The commands required to create the user, along with the expected login credentials for that user,
+     * or null if we are just going to use those from jclouds.
+     */
+    public static CreateUserStatements get(JcloudsLocation location, @Nullable Image image, ConfigBag config) {
+        //NB: private key is not installed remotely, just used to get/validate the public key
+        Preconditions.checkNotNull(location, "location argument required");
+        String user = Preconditions.checkNotNull(location.getUser(config), "user required");
+        final boolean isWindows = location.isWindows(image, config);
+        final String explicitLoginUser = config.get(JcloudsLocation.LOGIN_USER);
+        final String loginUser = groovyTruth(explicitLoginUser)
+                ? explicitLoginUser
+                : (image != null && image.getDefaultCredentials() != null)
+                        ? image.getDefaultCredentials().identity
+                        : null;
+        final boolean dontCreateUser = config.get(JcloudsLocation.DONT_CREATE_USER);
+        final boolean grantUserSudo = config.get(JcloudsLocation.GRANT_USER_SUDO);
+        final LocationConfigUtils.OsCredential credential = LocationConfigUtils.getOsCredential(config);
+        credential.checkNoErrors().logAnyWarnings();
+        final String passwordToSet =
+                Strings.isNonBlank(credential.getPassword()) ? credential.getPassword() : Identifiers.makeRandomId(12);
+        final List<Statement> statements = Lists.newArrayList();
+        LoginCredentials createdUserCreds = null;
+
+        if (dontCreateUser) {
+            // dontCreateUser:
+            // if caller has not specified a user, we'll just continue to use the loginUser;
+            // if caller *has*, we set up our credentials assuming that user and credentials already exist
+
+            if (Strings.isBlank(user)) {
+                // createdUserCreds returned from this method will be null;
+                // we will use the creds returned by jclouds on the node
+                LOG.info("Not setting up user {} (subsequently using loginUser {})", user, loginUser);
+                config.put(JcloudsLocation.USER, loginUser);
+
+            } else {
+                LOG.info("Not creating user {}, and not installing its password or authorizing keys (assuming it exists)", user);
+
+                if (credential.isUsingPassword()) {
+                    createdUserCreds = LoginCredentials.builder().user(user).password(credential.getPassword()).build();
+                    if (Boolean.FALSE.equals(config.get(JcloudsLocation.DISABLE_ROOT_AND_PASSWORD_SSH))) {
+                        statements.add(SshStatements.sshdConfig(ImmutableMap.of("PasswordAuthentication", "yes")));
+                    }
+                } else if (credential.hasKey()) {
+                    createdUserCreds = LoginCredentials.builder().user(user).privateKey(credential.getPrivateKeyData()).build();
+                }
+            }
+
+        } else if (isWindows) {
+            // TODO Generate statements to create the user.
+            // createdUserCreds returned from this method will be null;
+            // we will use the creds returned by jclouds on the node
+            LOG.warn("Not creating or configuring user on Windows VM, despite " + JcloudsLocation.DONT_CREATE_USER.getName() + " set to false");
+
+            // TODO extractVmCredentials() will use user:publicKeyData defaults, if we don't override this.
+            // For linux, how would we configure Brooklyn to use the node.getCredentials() - i.e. the version
+            // that the cloud automatically generated?
+            if (config.get(JcloudsLocation.USER) != null) config.put(JcloudsLocation.USER, "");
+            if (config.get(JcloudsLocation.PASSWORD) != null) config.put(JcloudsLocation.PASSWORD, "");
+            if (config.get(JcloudsLocation.PRIVATE_KEY_DATA) != null) config.put(JcloudsLocation.PRIVATE_KEY_DATA, "");
+            if (config.get(JcloudsLocation.PRIVATE_KEY_FILE) != null) config.put(JcloudsLocation.PRIVATE_KEY_FILE, "");
+            if (config.get(JcloudsLocation.PUBLIC_KEY_DATA) != null) config.put(JcloudsLocation.PUBLIC_KEY_DATA, "");
+            if (config.get(JcloudsLocation.PUBLIC_KEY_FILE) != null) config.put(JcloudsLocation.PUBLIC_KEY_FILE, "");
+
+        } else if (Strings.isBlank(user) || user.equals(loginUser) || user.equals(JcloudsLocation.ROOT_USERNAME)) {
+            boolean useKey = Strings.isNonBlank(credential.getPublicKeyData());
+
+            // For subsequent ssh'ing, we'll be using the loginUser
+            if (Strings.isBlank(user)) {
+                user = loginUser;
+                config.put(JcloudsLocation.USER, user);
+            }
+
+            // Using the pre-existing loginUser; setup the publicKey/password so can login as expected
+
+            // *Always* change the password (unless dontCreateUser was specified)
+            statements.add(new ReplaceShadowPasswordEntry(Sha512Crypt.function(), user, passwordToSet));
+            createdUserCreds = LoginCredentials.builder().user(user).password(passwordToSet).build();
+
+            if (useKey) {
+                // NB: further keys are added from config *after* user creation
+                statements.add(new AuthorizeRSAPublicKeys("~" + user + "/.ssh", ImmutableList.of(credential.getPublicKeyData()), null));
+                if (Strings.isNonBlank(credential.getPrivateKeyData())) {
+                    createdUserCreds = LoginCredentials.builder().user(user).privateKey(credential.getPrivateKeyData()).build();
+                }
+            }
+
+            if (!useKey || Boolean.FALSE.equals(config.get(JcloudsLocation.DISABLE_ROOT_AND_PASSWORD_SSH))) {
+                // ensure password is permitted for ssh
+                statements.add(SshStatements.sshdConfig(ImmutableMap.of("PasswordAuthentication", "yes")));
+                if (user.equals(JcloudsLocation.ROOT_USERNAME)) {
+                    statements.add(SshStatements.sshdConfig(ImmutableMap.of("PermitRootLogin", "yes")));
+                }
+            }
+
+        } else {
+            String pubKey = credential.getPublicKeyData();
+            String privKey = credential.getPrivateKeyData();
+
+            if (credential.isEmpty()) {
+                /*
+                 * TODO have an explicit `create_new_key_per_machine` config key.
+                 * error if privateKeyData is set in this case.
+                 * publicKeyData automatically added to EXTRA_SSH_KEY_URLS_TO_AUTH.
+                 *
+                 * if this config key is not set, use a key `brooklyn_id_rsa` and `.pub` in `MGMT_BASE_DIR`,
+                 * with permission 0600, creating it if necessary, and logging the fact that this was created.
+                 */
+                // TODO JcloudsLocation used to log this once only: loggedSshKeysHint.compareAndSet(false, true).
+                if (!config.containsKey(JcloudsLocation.PRIVATE_KEY_FILE)) {
+                    LOG.info("Default SSH keys not found or not usable; will create new keys for each machine. " +
+                                    "Create ~/.ssh/id_rsa or set {} / {} / {} as appropriate for this location " +
+                                    "if you wish to be able to log in without Brooklyn.",
+                            new Object[]{JcloudsLocation.PRIVATE_KEY_FILE.getName(), JcloudsLocation.PRIVATE_KEY_PASSPHRASE.getName(), JcloudsLocation.PASSWORD.getName()});
+                }
+                KeyPair newKeyPair = SecureKeys.newKeyPair();
+                pubKey = SecureKeys.toPub(newKeyPair);
+                privKey = SecureKeys.toPem(newKeyPair);
+                LOG.debug("Brooklyn key being created for " + user + " at new machine " + location + " is:\n" + privKey);
+            }
+
+            // Create the user
+            // note AdminAccess requires _all_ fields set, due to http://code.google.com/p/jclouds/issues/detail?id=1095
+            AdminAccess.Builder adminBuilder = AdminAccess.builder()
+                    .adminUsername(user)
+                    .grantSudoToAdminUser(grantUserSudo);
+            adminBuilder.cryptFunction(Sha512Crypt.function());
+
+            boolean useKey = Strings.isNonBlank(pubKey);
+            adminBuilder.cryptFunction(Sha512Crypt.function());
+
+            // always set this password; if not supplied, it will be a random string
+            adminBuilder.adminPassword(passwordToSet);
+            // log the password also, in case we need it
+            LOG.debug("Password '{}' being created for user '{}' at the machine we are about to provision in {}; {}",
+                    new Object[]{passwordToSet, user, location, useKey ? "however a key will be used to access it" : "this will be the only way to log in"});
+
+            if (grantUserSudo && config.get(JcloudsLocationConfig.DISABLE_ROOT_AND_PASSWORD_SSH)) {
+                // the default - set root password which we forget, because we have sudo acct
+                // (and lock out root and passwords from ssh)
+                adminBuilder.resetLoginPassword(true);
+                adminBuilder.loginPassword(Identifiers.makeRandomId(12));
+            } else {
+                adminBuilder.resetLoginPassword(false);
+                adminBuilder.loginPassword(Identifiers.makeRandomId(12) + "-ignored");
+            }
+
+            if (useKey) {
+                adminBuilder.authorizeAdminPublicKey(true).adminPublicKey(pubKey);
+            } else {
+                adminBuilder.authorizeAdminPublicKey(false).adminPublicKey(Identifiers.makeRandomId(12) + "-ignored");
+            }
+
+            // jclouds wants us to give it the private key, otherwise it might refuse to authorize the public key
+            // (in AdminAccess.build, if adminUsername != null && adminPassword != null);
+            // we don't want to give it the private key, but we *do* want the public key authorized;
+            // this code seems to trigger that.
+            // (we build the creds below)
+            adminBuilder.installAdminPrivateKey(false).adminPrivateKey(Identifiers.makeRandomId(12) + "-ignored");
+
+            // lock SSH means no root login and no passwordless login
+            // if we're using a password or we don't have sudo, then don't do this!
+            adminBuilder.lockSsh(useKey && grantUserSudo && config.get(JcloudsLocationConfig.DISABLE_ROOT_AND_PASSWORD_SSH));
+
+            statements.add(adminBuilder.build());
+
+            if (useKey) {
+                createdUserCreds = LoginCredentials.builder().user(user).privateKey(privKey).build();
+            } else if (passwordToSet != null) {
+                createdUserCreds = LoginCredentials.builder().user(user).password(passwordToSet).build();
+            }
+
+            if (!useKey || Boolean.FALSE.equals(config.get(JcloudsLocation.DISABLE_ROOT_AND_PASSWORD_SSH))) {
+                // ensure password is permitted for ssh
+                statements.add(SshStatements.sshdConfig(ImmutableMap.of("PasswordAuthentication", "yes")));
+            }
+        }
+
+        String customTemplateOptionsScript = config.get(JcloudsLocation.CUSTOM_TEMPLATE_OPTIONS_SCRIPT_CONTENTS);
+        if (Strings.isNonBlank(customTemplateOptionsScript)) {
+            statements.add(new LiteralStatement(customTemplateOptionsScript));
+        }
+
+        LOG.debug("Machine we are about to create in {} will be customized with: {}", location, statements);
+
+        return new CreateUserStatements(createdUserCreds, statements);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/c93a79de/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
index 14bab2d..c680815 100644
--- a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
@@ -30,7 +30,6 @@ import java.io.File;
 import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
-import java.security.KeyPair;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -45,7 +44,6 @@ import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
-
 import javax.annotation.Nullable;
 import javax.xml.ws.WebServiceException;
 
@@ -92,7 +90,6 @@ import org.apache.brooklyn.util.core.ClassLoaderUtils;
 import org.apache.brooklyn.util.core.ResourceUtils;
 import org.apache.brooklyn.util.core.config.ConfigBag;
 import org.apache.brooklyn.util.core.config.ResolvingConfigBag;
-import org.apache.brooklyn.util.core.crypto.SecureKeys;
 import org.apache.brooklyn.util.core.flags.MethodCoercions;
 import org.apache.brooklyn.util.core.flags.SetFromFlag;
 import org.apache.brooklyn.util.core.flags.TypeCoercions;
@@ -125,7 +122,6 @@ import org.apache.brooklyn.util.ssh.IptablesCommands.Chain;
 import org.apache.brooklyn.util.ssh.IptablesCommands.Policy;
 import org.apache.brooklyn.util.stream.Streams;
 import org.apache.brooklyn.util.text.ByteSizeStrings;
-import org.apache.brooklyn.util.text.Identifiers;
 import org.apache.brooklyn.util.text.KeyValueParser;
 import org.apache.brooklyn.util.text.StringPredicates;
 import org.apache.brooklyn.util.text.Strings;
@@ -149,7 +145,6 @@ import org.jclouds.compute.domain.OsFamily;
 import org.jclouds.compute.domain.Template;
 import org.jclouds.compute.domain.TemplateBuilder;
 import org.jclouds.compute.domain.TemplateBuilderSpec;
-import org.jclouds.compute.functions.Sha512Crypt;
 import org.jclouds.compute.options.TemplateOptions;
 import org.jclouds.domain.Credentials;
 import org.jclouds.domain.LocationScope;
@@ -157,12 +152,9 @@ import org.jclouds.domain.LoginCredentials;
 import org.jclouds.ec2.compute.options.EC2TemplateOptions;
 import org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOptions;
 import org.jclouds.rest.AuthorizationException;
-import org.jclouds.scriptbuilder.domain.LiteralStatement;
 import org.jclouds.scriptbuilder.domain.Statement;
 import org.jclouds.scriptbuilder.domain.StatementList;
 import org.jclouds.scriptbuilder.functions.InitAdminAccess;
-import org.jclouds.scriptbuilder.statements.login.AdminAccess;
-import org.jclouds.scriptbuilder.statements.login.ReplaceShadowPasswordEntry;
 import org.jclouds.scriptbuilder.statements.ssh.AuthorizeRSAPublicKeys;
 import org.jclouds.softlayer.compute.options.SoftLayerTemplateOptions;
 import org.jclouds.util.Predicates2;
@@ -225,14 +217,13 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
     @VisibleForTesting
     static final String AWS_VPC_HELP_URL = "http://brooklyn.apache.org/v/latest/ops/locations/index.html#ec2-classic-problems-with-vpc-only-hardware-instance-types";
 
-    private final AtomicBoolean loggedSshKeysHint = new AtomicBoolean(false);
     private final AtomicBoolean listedAvailableTemplatesOnNoSuchTemplate = new AtomicBoolean(false);
 
     private final Map<String,Map<String, ? extends Object>> tagMapping = Maps.newLinkedHashMap();
 
     @SetFromFlag // so it's persisted
     private final Map<MachineLocation,String> vmInstanceIds = Maps.newLinkedHashMap();
-    
+
     static { Networking.init(); }
 
     public JcloudsLocation() {
@@ -341,52 +332,52 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
     public boolean isWindows(Template template, ConfigBag config) {
         return isWindows(template.getImage(), config);
     }
-    
+
     /**
      * Whether VMs provisioned from this image will be Windows. Assume windows if the image
-     * explicitly says so, or if image does not tell us then fall back to whether the config 
+     * explicitly says so, or if image does not tell us then fall back to whether the config
      * explicitly says windows in {@link JcloudsLocationConfig#OS_FAMILY}.
-     * 
-     * Will first look at {@link JcloudsLocationConfig#OS_FAMILY_OVERRIDE}, to check if that 
+     *
+     * Will first look at {@link JcloudsLocationConfig#OS_FAMILY_OVERRIDE}, to check if that
      * is set. If so, no further checks are done: the value is compared against {@link OsFamily#WINDOWS}.
-     * 
-     * We believe the config (e.g. from brooklyn.properties) because for some clouds there is 
+     *
+     * We believe the config (e.g. from brooklyn.properties) because for some clouds there is
      * insufficient meta-data so the Image might not tell us. Thus a user can work around it
-     * by explicitly supplying configuration. 
+     * by explicitly supplying configuration.
      */
     public boolean isWindows(Image image, ConfigBag config) {
         OsFamily override = config.get(OS_FAMILY_OVERRIDE);
         if (override != null) return override == OsFamily.WINDOWS;
-        
+
         OsFamily confFamily = config.get(OS_FAMILY);
         OperatingSystem os = (image != null) ? image.getOperatingSystem() : null;
-        return (os != null && os.getFamily() != OsFamily.UNRECOGNIZED) 
-                ? (OsFamily.WINDOWS == os.getFamily()) 
+        return (os != null && os.getFamily() != OsFamily.UNRECOGNIZED)
+                ? (OsFamily.WINDOWS == os.getFamily())
                 : (OsFamily.WINDOWS == confFamily);
     }
 
     /**
      * Whether the given VM is Windows.
-     * 
-     * @see {@link #isWindows(Image, ConfigBag)}
+     *
+     * @see #isWindows(Image, ConfigBag)
      */
     public boolean isWindows(NodeMetadata node, ConfigBag config) {
         OsFamily override = config.get(OS_FAMILY_OVERRIDE);
         if (override != null) return override == OsFamily.WINDOWS;
-        
+
         OsFamily confFamily = config.get(OS_FAMILY);
         OperatingSystem os = (node != null) ? node.getOperatingSystem() : null;
-        return (os != null && os.getFamily() != OsFamily.UNRECOGNIZED) 
-                ? (OsFamily.WINDOWS == os.getFamily()) 
+        return (os != null && os.getFamily() != OsFamily.UNRECOGNIZED)
+                ? (OsFamily.WINDOWS == os.getFamily())
                 : (OsFamily.WINDOWS == confFamily);
     }
 
     public boolean isLocationFirewalldEnabled(SshMachineLocation location) {
-        int result = location.execCommands("checking if firewalld is active", 
+        int result = location.execCommands("checking if firewalld is active",
                 ImmutableList.of(IptablesCommands.firewalldServiceIsActive()));
         return result == 0;
     }
-    
+
     protected Semaphore getMachineCreationSemaphore() {
         return checkNotNull(getConfig(MACHINE_CREATION_SEMAPHORE), MACHINE_CREATION_SEMAPHORE.getName());
     }
@@ -615,7 +606,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
         Map<String, Object> baseTemplateOptions = config().get(TEMPLATE_OPTIONS);
         Map<String, Object> templateOptions = (Map<String, Object>) shallowMerge(Maybe.fromNullable(flagTemplateOptions), Maybe.fromNullable(baseTemplateOptions), TEMPLATE_OPTIONS).orNull();
         setup.put(TEMPLATE_OPTIONS, templateOptions);
-        
+
         Integer attempts = setup.get(MACHINE_CREATE_ATTEMPTS);
         List<Exception> exceptions = Lists.newArrayList();
         if (attempts == null || attempts < 1) attempts = 1;
@@ -667,7 +658,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
         Duration usableTimestamp = null;
         Duration customizedTimestamp = null;
         Stopwatch provisioningStopwatch = Stopwatch.createStarted();
-        
+
         try {
             LOG.info("Creating VM "+setup.getDescription()+" in "+this);
 
@@ -688,7 +679,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
             Template template;
             Collection<JcloudsLocationCustomizer> customizers = getCustomizers(setup);
             Collection<MachineLocationCustomizer> machineCustomizers = getMachineCustomizers(setup);
-            
+
             try {
                 // Setup the template
                 template = buildTemplate(computeService, setup, customizers);
@@ -708,10 +699,10 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                 // TODO it would be nice if this salt comes from the location's ID (but we don't know that yet as the ssh machine location isn't created yet)
                 // TODO in softlayer we want to control the suffix of the hostname which is 3 random hex digits
                 template.getOptions().getUserMetadata().put("Name", cloudMachineNamer.generateNewMachineUniqueNameFromGroupId(setup, groupId));
-                
+
                 if (setup.get(JcloudsLocationConfig.INCLUDE_BROOKLYN_USER_METADATA)) {
                     template.getOptions().getUserMetadata().put("brooklyn-user", System.getProperty("user.name"));
-                    
+
                     Object context = setup.get(CALLER_CONTEXT);
                     if (context instanceof Entity) {
                         Entity entity = (Entity)context;
@@ -722,9 +713,9 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                         template.getOptions().getUserMetadata().put("brooklyn-server-creation-date", Time.makeDateSimpleStampString());
                     }
                 }
-                
+
                 customizeTemplate(computeService, template, customizers);
-                
+
                 LOG.debug("jclouds using template {} / options {} to provision machine in {}",
                         new Object[] {template, template.getOptions(), setup.getDescription()});
 
@@ -741,7 +732,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
 
             boolean windows = isWindows(node, setup);
             boolean waitForConnectable = (windows) ? waitForWinRmable : waitForSshable;
-            
+
             if (windows) {
                 int newLoginPort = node.getLoginPort() == 22
                         ? (getConfig(WinRmMachineLocation.USE_HTTPS_WINRM) ? 5986 : 5985)
@@ -771,7 +762,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
             }
 
             LoginCredentials initialCredentials = node.getCredentials();
-            
+
             final HostAndPort managementHostAndPort = resolveManagementHostAndPort(
                     node, Optional.fromNullable(userCredentials), sshHostAndPortOverride, setup,
                     new ResolveOptions()
@@ -779,7 +770,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                             .expectConnectable(waitForConnectable)
                             .pollForFirstReachableAddress(!"false".equalsIgnoreCase(setup.get(POLL_FOR_FIRST_REACHABLE_ADDRESS)))
                             .propagatePollForReachableFailure(true));
-            
+
             if (skipJcloudsSshing) {
                 if (waitForConnectable) {
                     if (windows) {
@@ -895,7 +886,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                         }
                     }
                 }
-                
+
                 Boolean dontRequireTtyForSudo = setup.get(JcloudsLocationConfig.DONT_REQUIRE_TTY_FOR_SUDO);
                 if (Boolean.TRUE.equals(dontRequireTtyForSudo) ||
                         (dontRequireTtyForSudo == null && setup.get(DONT_CREATE_USER))) {
@@ -919,7 +910,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                                 (SshMachineLocation)machineLocation,
                                 "using urandom instead of random",
                                 Arrays.asList(
-                                        BashCommands.sudo("mv /dev/random /dev/random-real"), 
+                                        BashCommands.sudo("mv /dev/random /dev/random-real"),
                                         BashCommands.sudo("ln -s /dev/urandom /dev/random")));
                     }
                 }
@@ -948,7 +939,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                         LOG.warn("Ignoring DEPRECATED flag OPEN_IPTABLES on Windows location {}", machineLocation);
                     } else {
                         LOG.warn("Using DEPRECATED flag OPEN_IPTABLES (will not be supported in future versions) for {} at {}", machineLocation, this);
-                        
+
                         @SuppressWarnings("unchecked")
                         Iterable<Integer> inboundPorts = (Iterable<Integer>) setup.get(INBOUND_PORTS);
 
@@ -1002,7 +993,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                         LOG.warn("Ignoring DEPRECATED flag OPEN_IPTABLES on Windows location {}", machineLocation);
                     } else {
                         LOG.warn("Using DEPRECATED flag STOP_IPTABLES (will not be supported in future versions) for {} at {}", machineLocation, this);
-                        
+
                         customisationForLogging.add("stop iptables");
 
                         List<String> cmds = ImmutableList.<String>of();
@@ -1032,7 +1023,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                                 ImmutableList.of(new AuthorizeRSAPublicKeys(extraKeyDataToAuth).render(org.jclouds.scriptbuilder.domain.OsFamily.UNIX)));
                     }
                 }
-                
+
                 String extraKeyDataToAuth = setup.get(EXTRA_PUBLIC_KEY_DATA_TO_AUTH);
                 if (extraKeyDataToAuth!=null && !extraKeyDataToAuth.isEmpty()) {
                     if (windows) {
@@ -1084,7 +1075,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
             }
 
             return machineLocation;
-            
+
         } catch (Exception e) {
             if (e instanceof RunNodesException && ((RunNodesException)e).getNodeErrors().size() > 0) {
                 node = Iterables.get(((RunNodesException)e).getNodeErrors().keySet(), 0);
@@ -1101,7 +1092,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                 LOG.error(message);
                 e = new UserFacingException(message, e);
             }
-            
+
             LOG.error("Failed to start VM for "+setup.getDescription() + (destroyNode ? " (destroying)" : "")
                     + (node != null ? "; node "+node : "")
                     + " after "+Duration.of(provisioningStopwatch).toStringRounded()
@@ -1130,7 +1121,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
             throw Exceptions.propagate(e);
         }
     }
-    
+
     private void executeCommandThrowingOnError(SshMachineLocation loc, String name, List<String> commands) {
         executeCommandThrowingOnError(ImmutableMap.<String, Object>of(), loc, name, commands);
     }
@@ -1412,13 +1403,13 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                     }})
             .put(EXTRA_PUBLIC_KEY_DATA_TO_AUTH, new CustomizeTemplateOptions() {
                     public void apply(TemplateOptions t, ConfigBag props, Object v) {
-                        // this is unreliable: 
+                        // this is unreliable:
                         // * seems now (Aug 2016) to be run *before* the TO.runScript which creates the user,
                         // so is installed for the initial login user not the created user
                         // * not supported in GCE (it uses it as the login public key, see email to jclouds list, 29 Aug 2015)
                         // so only works if you also overrideLoginPrivateKey
                         // --
-                        // for this reason we also inspect these ourselves 
+                        // for this reason we also inspect these ourselves
                         // along with EXTRA_PUBLIC_KEY_URLS_TO_AUTH
                         // and install after creation;
                         // --
@@ -1514,12 +1505,12 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                         if (t instanceof AWSEC2TemplateOptions) {
                             // subnet ID is the sensible interpretation of network name in EC2
                             ((AWSEC2TemplateOptions)t).subnetId((String)v);
-                            
+
                         } else {
                             if (isGoogleComputeTemplateOptions(t)) {
                                 // no warning needed
                                 // we think this is the only jclouds endpoint which supports this option
-                                
+
                             } else if (t instanceof SoftLayerTemplateOptions) {
                                 LOG.warn("networkName is not be supported in SoftLayer; use `templateOptions` with `primaryNetworkComponentNetworkVlanId` or `primaryNetworkBackendComponentNetworkVlanId`");
                             } else if (!(t instanceof CloudStackTemplateOptions) && !(t instanceof NovaTemplateOptions)) {
@@ -1529,7 +1520,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
 //                                Openstack Nova uses securityGroupNames which is marked as @deprecated (suggests to use groups which is maybe even more confusing)
 //                                Azure supports the custom networkSecurityGroupName
                             }
-                            
+
                             t.networks((String)v);
                         }
                     }})
@@ -1538,7 +1529,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                         if (t instanceof SoftLayerTemplateOptions) {
                             ((SoftLayerTemplateOptions)t).domainName(TypeCoercions.coerce(v, String.class));
                         } else {
-                            LOG.info("ignoring domain-name({}) in VM creation because not supported for cloud/type ({})", v, t);                            
+                            LOG.info("ignoring domain-name({}) in VM creation because not supported for cloud/type ({})", v, t);
                         }
                     }})
             .put(TEMPLATE_OPTIONS, new CustomizeTemplateOptions() {
@@ -1610,7 +1601,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
     }
 
     /**
-     * If the ImageChooser is a string, then try instantiating a class with that name (in the same 
+     * If the ImageChooser is a string, then try instantiating a class with that name (in the same
      * way as we do for {@link #getCloudMachineNamer(ConfigBag)}, for example). Otherwise, assume
      * that convention TypeCoercions will work.
      */
@@ -1769,7 +1760,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                 }
             }
         }
-               
+
         for (Map.Entry<ConfigKey<?>, CustomizeTemplateOptions> entry : SUPPORTED_TEMPLATE_OPTIONS_PROPERTIES.entrySet()) {
             ConfigKey<?> key = entry.getKey();
             CustomizeTemplateOptions code = entry.getValue();
@@ -1786,7 +1777,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
         if (Strings.isBlank(s)) s = config().get(NAMED_SPEC_NAME);
         if (Strings.isBlank(s)) s = config().get(FINAL_SPEC);
         if (Strings.isBlank(s)) s = getDisplayName();
-        
+
         String s2 = "";
         String provider = getProvider();
         if (Strings.isBlank(s) || (Strings.isNonBlank(provider) && !s.toLowerCase().contains(provider.toLowerCase())))
@@ -1810,7 +1801,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
         // things are bad if we get to this point!
         return toString();
     }
-    
+
     protected void logAvailableTemplates(ConfigBag config) {
         LOG.info("Loading available images at "+this+" for reference...");
         ConfigBag m1 = ConfigBag.newInstanceCopying(config);
@@ -1860,7 +1851,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
         sshProps.remove("privateKeyData");
         sshProps.remove("privateKeyFile");
         sshProps.remove("privateKeyPassphrase");
-        
+
         if (initialPassword.isPresent()) sshProps.put("password", initialPassword.get());
         if (initialPrivateKey.isPresent()) sshProps.put("privateKeyData", initialPrivateKey.get());
 
@@ -1904,11 +1895,13 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
     /**
      * Create the user immediately - executing ssh commands as required.
      */
-    protected LoginCredentials createUser(ComputeService computeService, NodeMetadata node, HostAndPort managementHostAndPort, LoginCredentials initialCredentials, ConfigBag config) {
+    protected LoginCredentials createUser(
+            ComputeService computeService, NodeMetadata node, HostAndPort managementHostAndPort,
+            LoginCredentials initialCredentials, ConfigBag config) {
         Image image = (node.getImageId() != null) ? computeService.getImage(node.getImageId()) : null;
-        UserCreation userCreation = createUserStatements(image, config);
+        CreateUserStatements userCreation = createUserStatements(image, config);
 
-        if (!userCreation.statements.isEmpty()) {
+        if (!userCreation.statements().isEmpty()) {
             // If unsure of OS family, default to unix for rendering statements.
             org.jclouds.scriptbuilder.domain.OsFamily scriptOsFamily;
             if (isWindows(node, config)) {
@@ -1920,10 +1913,10 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
             boolean windows = isWindows(node, config);
 
             if (windows) {
-                LOG.warn("Unable to execute statements on WinRM in JcloudsLocation; skipping for "+node+": "+userCreation.statements);
+                LOG.warn("Unable to execute statements on WinRM in JcloudsLocation; skipping for "+node+": "+userCreation.statements());
             } else {
                 List<String> commands = Lists.newArrayList();
-                for (Statement statement : userCreation.statements) {
+                for (Statement statement : userCreation.statements()) {
                     InitAdminAccess initAdminAccess = new InitAdminAccess(new AdminAccessConfiguration.Default());
                     initAdminAccess.visit(statement);
                     commands.add(statement.render(scriptOsFamily));
@@ -1960,7 +1953,21 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
             }
         }
 
-        return userCreation.createdUserCredentials;
+        return userCreation.credentials();
+    }
+
+    /**
+     * Set up the TemplateOptions to create the user.
+     */
+    protected LoginCredentials initTemplateForCreateUser(Template template, ConfigBag config) {
+        CreateUserStatements userCreation = createUserStatements(template.getImage(), config);
+
+        if (!userCreation.statements().isEmpty()) {
+            TemplateOptions options = template.getOptions();
+            options.runScript(new StatementList(userCreation.statements()));
+        }
+
+        return userCreation.credentials();
     }
 
     private static class ResolveOptions {
@@ -1968,7 +1975,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
         boolean expectConnectable;
         boolean isWindows;
         boolean propagatePollForReachableFailure;
-        
+
         ResolveOptions pollForFirstReachableAddress(boolean val) {
             pollForFirstReachableAddress = val;
             return this;
@@ -1986,17 +1993,17 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
             return this;
         }
     }
-    
+
     /**
-     * Infers the hostAndPort to use for subsequent creation of the  
+     * Infers the hostAndPort to use for subsequent creation of the
      * {@link JcloudsSshMachineLocation} or {@link JcloudsWinRmMachineLocation}.
      * This is expected to be the login host:port, for connecting to the VM
      * via ssh or WinRM.
-     * 
-     * However, some VMs provisioned will not be sshable or reachable at all. 
-     * In such cases, this method will likely return the first address returned by 
+     *
+     * However, some VMs provisioned will not be sshable or reachable at all.
+     * In such cases, this method will likely return the first address returned by
      * jclouds.
-     * 
+     *
      * For AWS, if we are allowed to SSH to the VM to find out its DNS name, then we'll
      * return that fully qualified name (which we expect to be reachable from inside
      * and outside the AWS region).
@@ -2020,8 +2027,8 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
             return HostAndPort.fromParts(hostAndPortOverride.get().getHostText(), port);
         }
         if (options.expectConnectable && userCredentials.isPresent() && "aws-ec2".equals(provider) && lookupAwsHostname) {
-            // Treat AWS as a special case because the DNS fully qualified hostname in AWS is 
-            // (normally?!) a good way to refer to the VM from both inside and outside of the 
+            // Treat AWS as a special case because the DNS fully qualified hostname in AWS is
+            // (normally?!) a good way to refer to the VM from both inside and outside of the
             // region.
             Maybe<String> result = getHostnameAws(node, hostAndPortOverride, Suppliers.ofInstance(userCredentials.get()), config);
             if (result.isPresent()) {
@@ -2064,261 +2071,26 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
         }
     }
 
-    /**
-     * Setup the TemplateOptions to create the user.
-     */
-    protected LoginCredentials initTemplateForCreateUser(Template template, ConfigBag config) {
-        UserCreation userCreation = createUserStatements(template.getImage(), config);
-
-        if (userCreation.statements.size() > 0) {
-            TemplateOptions options = template.getOptions();
-            options.runScript(new StatementList(userCreation.statements));
-        }
-
-        return userCreation.createdUserCredentials;
-    }
-
-    protected static class UserCreation {
+    /** @deprecated since 0.11.0 use {@link CreateUserStatements} instead. */
+    @Deprecated
+    protected static class UserCreation extends CreateUserStatements  {
         public final LoginCredentials createdUserCredentials;
         public final List<Statement> statements;
 
         public UserCreation(LoginCredentials creds, List<Statement> statements) {
-            this.createdUserCredentials = creds;
-            this.statements = statements;
+            super(creds, statements);
+            this.createdUserCredentials = super.credentials();
+            this.statements = super.statements();
         }
     }
 
-    /**
-     * Returns the commands required to create the user, to be used for connecting (e.g. over ssh)
-     * to the machine; also returns the expected login credentials.
-     * <p>
-     * The returned login credentials may be null if we haven't done any user-setup and no specific
-     * user was supplied (i.e. if {@code dontCreateUser} was true and {@code user} was null or blank).
-     * In which case, the caller should use the jclouds node's login credentials.
-     * <p>
-     * There are quite a few configuration options. Depending on their values, the user-creation
-     * behaves differently:
-     * <ul>
-     *   <li>{@code dontCreateUser} says not to run any user-setup commands at all. If {@code user} is
-     *       non-empty (including with the default value), then that user will subsequently be used,
-     *       otherwise the (inferred) {@code loginUser} will be used.
-     *   <li>{@code loginUser} refers to the existing user that jclouds should use when setting up the VM.
-     *       Normally this will be inferred from the image (i.e. doesn't need to be explicitly set), but sometimes
-     *       the image gets it wrong so this can be a handy override.
-     *   <li>{@code user} is the username for brooklyn to subsequently use when ssh'ing to the machine.
-     *       If not explicitly set, its value will default to the username of the user running brooklyn.
-     *       <ul>
-     *         <li>If the {@code user} value is null or empty, then the (inferred) {@code loginUser} will
-     *             subsequently be used, setting up the password/authorizedKeys for that loginUser.
-     *         <li>If the {@code user} is "root", then setup the password/authorizedKeys for root.
-     *         <li>If the {@code user} equals the (inferred) {@code loginUser}, then don't try to create this
-     *             user but instead just setup the password/authorizedKeys for the user.
-     *         <li>Otherwise create the given user, setting up the password/authorizedKeys (unless
-     *             {@code dontCreateUser} is set, obviously).
-     *       </ul>
-     *   <li>{@code publicKeyData} is the key to authorize (i.e. add to .ssh/authorized_keys),
-     *       if not null or blank. Note the default is to use {@code ~/.ssh/id_rsa.pub} or {@code ~/.ssh/id_dsa.pub}
-     *       if either of those files exist for the user running brooklyn.
-     *       Related is {@code publicKeyFile}, which is used to populate publicKeyData.
-     *   <li>{@code password} is the password to set for the user. If null or blank, then a random password
-     *       will be auto-generated and set.
-     *   <li>{@code privateKeyData} is the key to use when subsequent ssh'ing, if not null or blank.
-     *       Note the default is to use {@code ~/.ssh/id_rsa} or {@code ~/.ssh/id_dsa}.
-     *       The subsequent preferences for ssh'ing are:
-     *       <ul>
-     *         <li>Use the {@code privateKeyData} if not null or blank (including if using default)
-     *         <li>Use the {@code password} (or the auto-generated password if that is blank).
-     *       </ul>
-     *   <li>{@code grantUserSudo} determines whether or not the created user may run the sudo command.</li>
-     * </ul>
-     *
-     * @param image  The image being used to create the VM
-     * @param config Configuration for creating the VM
-     * @return       The commands required to create the user, along with the expected login credentials for that user,
-     * or null if we are just going to use those from jclouds.
-     */
+    /** @deprecated since 0.11.0 return type will be changed to {@link CreateUserStatements} in a future release. */
+    @Deprecated
     protected UserCreation createUserStatements(@Nullable Image image, ConfigBag config) {
-        //NB: private key is not installed remotely, just used to get/validate the public key
-
-        boolean windows = isWindows(image, config);
-        String user = getUser(config);
-        String explicitLoginUser = config.get(LOGIN_USER);
-        String loginUser = groovyTruth(explicitLoginUser) ? explicitLoginUser : (image != null && image.getDefaultCredentials() != null) ? image.getDefaultCredentials().identity : null;
-        boolean dontCreateUser = config.get(DONT_CREATE_USER);
-        boolean grantUserSudo = config.get(GRANT_USER_SUDO);
-        OsCredential credential = LocationConfigUtils.getOsCredential(config);
-        credential.checkNoErrors().logAnyWarnings();
-        String passwordToSet = Strings.isNonBlank(credential.getPassword()) ? credential.getPassword() : Identifiers.makeRandomId(12);
-        List<Statement> statements = Lists.newArrayList();
-        LoginCredentials createdUserCreds = null;
-
-        if (dontCreateUser) {
-            // dontCreateUser:
-            // if caller has not specified a user, we'll just continue to use the loginUser;
-            // if caller *has*, we set up our credentials assuming that user and credentials already exist
-
-            if (Strings.isBlank(user)) {
-                // createdUserCreds returned from this method will be null;
-                // we will use the creds returned by jclouds on the node
-                LOG.info("Not setting up any user (subsequently using loginUser {})", user, loginUser);
-                config.put(USER, loginUser);
-
-            } else {
-                LOG.info("Not creating user {}, and not installing its password or authorizing keys (assuming it exists)", user);
-
-                if (credential.isUsingPassword()) {
-                    createdUserCreds = LoginCredentials.builder().user(user).password(credential.getPassword()).build();
-                    if (Boolean.FALSE.equals(config.get(DISABLE_ROOT_AND_PASSWORD_SSH))) {
-                        statements.add(org.jclouds.scriptbuilder.statements.ssh.SshStatements.sshdConfig(ImmutableMap.of("PasswordAuthentication", "yes")));
-                    }
-                } else if (credential.hasKey()) {
-                    createdUserCreds = LoginCredentials.builder().user(user).privateKey(credential.getPrivateKeyData()).build();
-                }
-            }
-
-        } else if (windows) {
-            // TODO Generate statements to create the user.
-            // createdUserCreds returned from this method will be null;
-            // we will use the creds returned by jclouds on the node
-            LOG.warn("Not creating or configuring user on Windows VM, despite "+DONT_CREATE_USER.getName()+" set to false");
-
-            // TODO extractVmCredentials() will use user:publicKeyData defaults, if we don't override this.
-            // For linux, how would we configure Brooklyn to use the node.getCredentials() - i.e. the version
-            // that the cloud automatically generated?
-            if (config.get(USER) != null) config.put(USER, "");
-            if (config.get(PASSWORD) != null) config.put(PASSWORD, "");
-            if (config.get(PRIVATE_KEY_DATA) != null) config.put(PRIVATE_KEY_DATA, "");
-            if (config.get(PRIVATE_KEY_FILE) != null) config.put(PRIVATE_KEY_FILE, "");
-            if (config.get(PUBLIC_KEY_DATA) != null) config.put(PUBLIC_KEY_DATA, "");
-            if (config.get(PUBLIC_KEY_FILE) != null) config.put(PUBLIC_KEY_FILE, "");
-
-        } else if (Strings.isBlank(user) || user.equals(loginUser) || user.equals(ROOT_USERNAME)) {
-            boolean useKey = Strings.isNonBlank(credential.getPublicKeyData());
-            
-            // For subsequent ssh'ing, we'll be using the loginUser
-            if (Strings.isBlank(user)) {
-                user = loginUser;
-                config.put(USER, user);
-            }
-
-            // Using the pre-existing loginUser; setup the publicKey/password so can login as expected
-
-            // *Always* change the password (unless dontCreateUser was specified)
-            statements.add(new ReplaceShadowPasswordEntry(Sha512Crypt.function(), user, passwordToSet));
-            createdUserCreds = LoginCredentials.builder().user(user).password(passwordToSet).build();
-
-            if (useKey) {
-                // NB: further keys are added from config *after* user creation
-                statements.add(new AuthorizeRSAPublicKeys("~"+user+"/.ssh", ImmutableList.of(credential.getPublicKeyData()), null));
-                if (Strings.isNonBlank(credential.getPrivateKeyData())) {
-                    createdUserCreds = LoginCredentials.builder().user(user).privateKey(credential.getPrivateKeyData()).build();
-                }
-            }
-            
-            if (!useKey || Boolean.FALSE.equals(config.get(DISABLE_ROOT_AND_PASSWORD_SSH))) {
-                // ensure password is permitted for ssh
-                statements.add(org.jclouds.scriptbuilder.statements.ssh.SshStatements.sshdConfig(ImmutableMap.of("PasswordAuthentication", "yes")));
-                if (user.equals(ROOT_USERNAME)) {
-                    statements.add(org.jclouds.scriptbuilder.statements.ssh.SshStatements.sshdConfig(ImmutableMap.of("PermitRootLogin", "yes")));
-                }
-            }
-
-        } else {
-            String pubKey = credential.getPublicKeyData();
-            String privKey = credential.getPrivateKeyData();
-
-            if (credential.isEmpty()) {
-                /*
-                 * TODO have an explicit `create_new_key_per_machine` config key.
-                 * error if privateKeyData is set in this case.
-                 * publicKeyData automatically added to EXTRA_SSH_KEY_URLS_TO_AUTH.
-                 *
-                 * if this config key is not set, use a key `brooklyn_id_rsa` and `.pub` in `MGMT_BASE_DIR`,
-                 * with permission 0600, creating it if necessary, and logging the fact that this was created.
-                 */
-                if (!config.containsKey(PRIVATE_KEY_FILE) && loggedSshKeysHint.compareAndSet(false, true)) {
-                    LOG.info("Default SSH keys not found or not usable; will create new keys for each machine. "
-                        + "Create ~/.ssh/id_rsa or "
-                        + "set "+PRIVATE_KEY_FILE.getName()+" / "+PRIVATE_KEY_PASSPHRASE.getName()+" / "+PASSWORD.getName()+" "
-                        + "as appropriate for this location if you wish to be able to log in without Brooklyn.");
-                }
-                KeyPair newKeyPair = SecureKeys.newKeyPair();
-                pubKey = SecureKeys.toPub(newKeyPair);
-                privKey = SecureKeys.toPem(newKeyPair);
-                LOG.debug("Brooklyn key being created for "+user+" at new machine "+this+" is:\n"+privKey);
-            }
-            // ensure credential is not used any more, as we have extracted all useful info
-            credential = null;
-
-            // Create the user
-            // note AdminAccess requires _all_ fields set, due to http://code.google.com/p/jclouds/issues/detail?id=1095
-            AdminAccess.Builder adminBuilder = AdminAccess.builder()
-                    .adminUsername(user)
-                    .grantSudoToAdminUser(grantUserSudo);
-            adminBuilder.cryptFunction(Sha512Crypt.function());
-
-            boolean useKey = Strings.isNonBlank(pubKey);
-            adminBuilder.cryptFunction(Sha512Crypt.function());
-
-            // always set this password; if not supplied, it will be a random string
-            adminBuilder.adminPassword(passwordToSet);
-            // log the password also, in case we need it
-            LOG.debug("Password '"+passwordToSet+"' being created for user '"+user+"' at the machine we are about to provision in "+this+"; "+
-                (useKey ? "however a key will be used to access it" : "this will be the only way to log in"));
-
-            if (grantUserSudo && config.get(JcloudsLocationConfig.DISABLE_ROOT_AND_PASSWORD_SSH)) {
-                // the default - set root password which we forget, because we have sudo acct
-                // (and lock out root and passwords from ssh)
-                adminBuilder.resetLoginPassword(true);
-                adminBuilder.loginPassword(Identifiers.makeRandomId(12));
-            } else {
-                adminBuilder.resetLoginPassword(false);
-                adminBuilder.loginPassword(Identifiers.makeRandomId(12)+"-ignored");
-            }
-
-            if (useKey) {
-                adminBuilder.authorizeAdminPublicKey(true).adminPublicKey(pubKey);
-            } else {
-                adminBuilder.authorizeAdminPublicKey(false).adminPublicKey(Identifiers.makeRandomId(12)+"-ignored");
-            }
-
-            // jclouds wants us to give it the private key, otherwise it might refuse to authorize the public key
-            // (in AdminAccess.build, if adminUsername != null && adminPassword != null);
-            // we don't want to give it the private key, but we *do* want the public key authorized;
-            // this code seems to trigger that.
-            // (we build the creds below)
-            adminBuilder.installAdminPrivateKey(false).adminPrivateKey(Identifiers.makeRandomId(12)+"-ignored");
-
-            // lock SSH means no root login and no passwordless login
-            // if we're using a password or we don't have sudo, then don't do this!
-            adminBuilder.lockSsh(useKey && grantUserSudo && config.get(JcloudsLocationConfig.DISABLE_ROOT_AND_PASSWORD_SSH));
-
-            statements.add(adminBuilder.build());
-
-            if (useKey) {
-                createdUserCreds = LoginCredentials.builder().user(user).privateKey(privKey).build();
-            } else if (passwordToSet!=null) {
-                createdUserCreds = LoginCredentials.builder().user(user).password(passwordToSet).build();
-            }
-            
-            if (!useKey || Boolean.FALSE.equals(config.get(DISABLE_ROOT_AND_PASSWORD_SSH))) {
-                // ensure password is permitted for ssh
-                statements.add(org.jclouds.scriptbuilder.statements.ssh.SshStatements.sshdConfig(ImmutableMap.of("PasswordAuthentication", "yes")));
-            }
-        }
-
-        String customTemplateOptionsScript = config.get(CUSTOM_TEMPLATE_OPTIONS_SCRIPT_CONTENTS);
-        if (Strings.isNonBlank(customTemplateOptionsScript)) {
-            statements.add(new LiteralStatement(customTemplateOptionsScript));
-        }
-
-        LOG.debug("Machine we are about to create in "+this+" will be customized with: "+
-            statements);
-
-        return new UserCreation(createdUserCreds, statements);  
+        CreateUserStatements userStatements = CreateUserStatements.get(this, image, config);
+        return new UserCreation(userStatements.credentials(), userStatements.statements());
     }
 
-
     // ----------------- registering existing machines ------------------------
 
     /**
@@ -3537,7 +3309,6 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
         return o.toString();
     }
 
-
     protected static double toDouble(Object v) {
         if (v instanceof Number) {
             return ((Number)v).doubleValue();

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/c93a79de/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationTest.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationTest.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationTest.java
index 8b04f93..119c19b 100644
--- a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationTest.java
+++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationTest.java
@@ -25,7 +25,6 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
-
 import javax.annotation.Nullable;
 
 import org.apache.brooklyn.api.location.LocationSpec;
@@ -41,7 +40,6 @@ import org.apache.brooklyn.core.location.cloud.names.CustomMachineNamer;
 import org.apache.brooklyn.core.location.geo.HostGeoInfo;
 import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
 import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
-import org.apache.brooklyn.location.jclouds.JcloudsLocation.UserCreation;
 import org.apache.brooklyn.test.Asserts;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.core.config.ConfigBag;
@@ -580,8 +578,8 @@ public class JcloudsLocationTest implements JcloudsLocationConfig {
                         .put(JcloudsLocationConfig.USER, "bob").put(JcloudsLocationConfig.LOGIN_USER_PASSWORD, "b0b")
                         .putAll(config).build());
 
-        UserCreation creation = jl.createUserStatements(null, jl.config().getBag());
-        return new StatementList(creation.statements).render(OsFamily.UNIX);
+        CreateUserStatements creation = CreateUserStatements.get(jl, null, jl.config().getBag());
+        return new StatementList(creation.statements()).render(OsFamily.UNIX);
     }
     
     @Test