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

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

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