You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by na...@apache.org on 2017/06/15 10:30:31 UTC
jclouds git commit: Improve duplicate ssh key check in Packet
Repository: jclouds
Updated Branches:
refs/heads/master eaf10f10a -> b77ea0695
Improve duplicate ssh key check in Packet
Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/b77ea069
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/b77ea069
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/b77ea069
Branch: refs/heads/master
Commit: b77ea0695007a408c6775b034ddb0223966cca82
Parents: eaf10f1
Author: Ignasi Barrera <na...@apache.org>
Authored: Thu Jun 15 12:30:17 2017 +0200
Committer: Ignasi Barrera <na...@apache.org>
Committed: Thu Jun 15 12:30:17 2017 +0200
----------------------------------------------------------------------
.../strategy/CreateSshKeysThenCreateNodes.java | 289 ++++++++++---------
1 file changed, 154 insertions(+), 135 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/jclouds/blob/b77ea069/providers/packet/src/main/java/org/jclouds/packet/compute/strategy/CreateSshKeysThenCreateNodes.java
----------------------------------------------------------------------
diff --git a/providers/packet/src/main/java/org/jclouds/packet/compute/strategy/CreateSshKeysThenCreateNodes.java b/providers/packet/src/main/java/org/jclouds/packet/compute/strategy/CreateSshKeysThenCreateNodes.java
index 148079f..09f0e90 100644
--- a/providers/packet/src/main/java/org/jclouds/packet/compute/strategy/CreateSshKeysThenCreateNodes.java
+++ b/providers/packet/src/main/java/org/jclouds/packet/compute/strategy/CreateSshKeysThenCreateNodes.java
@@ -48,6 +48,7 @@ import org.jclouds.packet.domain.SshKey;
import org.jclouds.ssh.SshKeyPairGenerator;
import org.jclouds.ssh.SshKeys;
+import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.Multimap;
@@ -65,152 +66,170 @@ import static com.google.common.collect.Iterables.size;
@Singleton
public class CreateSshKeysThenCreateNodes extends CreateNodesWithGroupEncodedIntoNameThenAddToSet {
- @Resource
- @Named(ComputeServiceConstants.COMPUTE_LOGGER)
- protected Logger logger = Logger.NULL;
-
- private final PacketApi api;
- private final SshKeyPairGenerator keyGenerator;
-
- @Inject
- protected CreateSshKeysThenCreateNodes(
- CreateNodeWithGroupEncodedIntoName addNodeWithGroupStrategy,
- ListNodesStrategy listNodesStrategy,
- GroupNamingConvention.Factory namingConvention,
- @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor,
- CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.Factory customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory,
- PacketApi api, SshKeyPairGenerator keyGenerator) {
- super(addNodeWithGroupStrategy, listNodesStrategy, namingConvention, userExecutor,
- customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory);
- this.api = api;
- this.keyGenerator = keyGenerator;
- }
-
- @Override
- public Map<?, ListenableFuture<Void>> execute(String group, int count, Template template,
- Set<NodeMetadata> goodNodes, Map<NodeMetadata, Exception> badNodes,
- Multimap<NodeMetadata, CustomizationResponse> customizationResponses) {
-
- PacketTemplateOptions options = template.getOptions().as(PacketTemplateOptions.class);
- Set<String> generatedSshKeyIds = Sets.newHashSet();
-
- // If no key has been configured, generate a key pair
- if (Strings.isNullOrEmpty(options.getPublicKey())) {
- generateKeyPairAndAddKeyToSet(options, generatedSshKeyIds, group);
- }
-
- // If there is a script to run in the node, make sure a private key has
- // been configured so jclouds will be able to access the node
- if (options.getRunScript() != null && Strings.isNullOrEmpty(options.getLoginPrivateKey())) {
- logger.warn(">> A runScript has been configured but no SSH key has been provided."
- + " Authentication will delegate to the ssh-agent");
- }
-
- // If there is a key configured, then make sure there is a key pair for it
- if (!Strings.isNullOrEmpty(options.getPublicKey())) {
- createKeyPairForPublicKeyInOptionsAndAddToSet(options, generatedSshKeyIds);
- }
-
- Map<?, ListenableFuture<Void>> responses = super.execute(group, count, template, goodNodes, badNodes,
- customizationResponses);
-
- // Key pairs in Packet are only required to create the devices. They aren't used anymore so it is better
- // to delete the auto-generated key pairs at this point where we know exactly which ones have been
- // auto-generated by jclouds.
- registerAutoGeneratedKeyPairCleanupCallbacks(responses, generatedSshKeyIds);
-
- return responses;
- }
-
- private void createKeyPairForPublicKeyInOptionsAndAddToSet(PacketTemplateOptions options,
- Set<String> generatedSshKeyIds) {
- logger.debug(">> checking if the key pair already exists...");
-
- PublicKey userKey;
- Iterable<String> parts = Splitter.on(' ').split(options.getPublicKey());
- checkArgument(size(parts) >= 2, "bad format, should be: ssh-rsa AAAAB3...");
- String type = get(parts, 0);
-
- try {
- if ("ssh-rsa".equals(type)) {
- RSAPublicKeySpec spec = SshKeys.publicKeySpecFromOpenSSH(options.getPublicKey());
- userKey = KeyFactory.getInstance("RSA").generatePublic(spec);
- } else {
- throw new IllegalArgumentException("bad format, ssh-rsa is only supported");
- }
- } catch (InvalidKeySpecException ex) {
- throw propagate(ex);
- } catch (NoSuchAlgorithmException ex) {
- throw propagate(ex);
- }
- String label = computeFingerprint(userKey);
- SshKey key = api.sshKeyApi().get(label);
-
- if (key == null) {
- logger.debug(">> key pair not found. creating a new key pair %s ...", label);
- SshKey newKey = api.sshKeyApi().create(label, options.getPublicKey());
- logger.debug(">> key pair created! %s", newKey);
- generatedSshKeyIds.add(newKey.id());
- } else {
- logger.debug(">> key pair found! %s", key);
- generatedSshKeyIds.add(key.id());
+ @Resource
+ @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+ protected Logger logger = Logger.NULL;
+
+ private final PacketApi api;
+ private final SshKeyPairGenerator keyGenerator;
+
+ @Inject
+ protected CreateSshKeysThenCreateNodes(
+ CreateNodeWithGroupEncodedIntoName addNodeWithGroupStrategy,
+ ListNodesStrategy listNodesStrategy,
+ GroupNamingConvention.Factory namingConvention,
+ @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor,
+ CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.Factory customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory,
+ PacketApi api, SshKeyPairGenerator keyGenerator) {
+ super(addNodeWithGroupStrategy, listNodesStrategy, namingConvention, userExecutor,
+ customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory);
+ this.api = api;
+ this.keyGenerator = keyGenerator;
+ }
+
+ @Override
+ public Map<?, ListenableFuture<Void>> execute(String group, int count, Template template,
+ Set<NodeMetadata> goodNodes, Map<NodeMetadata, Exception> badNodes,
+ Multimap<NodeMetadata, CustomizationResponse> customizationResponses) {
+
+ PacketTemplateOptions options = template.getOptions().as(PacketTemplateOptions.class);
+ Set<String> generatedSshKeyIds = Sets.newHashSet();
+
+ // If no key has been configured, generate a key pair
+ if (Strings.isNullOrEmpty(options.getPublicKey())) {
+ generateKeyPairAndAddKeyToSet(options, generatedSshKeyIds, group);
+ }
+
+ // If there is a script to run in the node, make sure a private key has
+ // been configured so jclouds will be able to access the node
+ if (options.getRunScript() != null && Strings.isNullOrEmpty(options.getLoginPrivateKey())) {
+ logger.warn(">> A runScript has been configured but no SSH key has been provided."
+ + " Authentication will delegate to the ssh-agent");
}
- }
- private void generateKeyPairAndAddKeyToSet(PacketTemplateOptions options, Set<String> generatedSshKeyIds, String prefix) {
- logger.debug(">> creating default keypair for node...");
+ // If there is a key configured, then make sure there is a key pair for it
+ if (!Strings.isNullOrEmpty(options.getPublicKey())) {
+ createKeyPairForPublicKeyInOptionsAndAddToSet(options, generatedSshKeyIds);
+ }
- Map<String, String> defaultKeys = keyGenerator.get();
+ Map<?, ListenableFuture<Void>> responses = super.execute(group, count, template, goodNodes, badNodes,
+ customizationResponses);
- SshKey sshKey = api.sshKeyApi().create(prefix + System.getProperty("user.name"), defaultKeys.get("public"));
- generatedSshKeyIds.add(sshKey.id());
- logger.debug(">> keypair created! %s", sshKey);
+ // Key pairs in Packet are only required to create the devices. They
+ // aren't used anymore so it is better
+ // to delete the auto-generated key pairs at this point where we know
+ // exactly which ones have been
+ // auto-generated by jclouds.
+ registerAutoGeneratedKeyPairCleanupCallbacks(responses, generatedSshKeyIds);
- // If a private key has not been explicitly set, configure the generated one
- if (Strings.isNullOrEmpty(options.getLoginPrivateKey())) {
- options.overrideLoginPrivateKey(defaultKeys.get("private"));
- }
- }
+ return responses;
+ }
- private void registerAutoGeneratedKeyPairCleanupCallbacks(Map<?, ListenableFuture<Void>> responses,
- final Set<String> generatedSshKeyIds) {
- // The Futures.allAsList fails immediately if some of the futures fail. The Futures.successfulAsList, however,
- // returns a list containing the results or 'null' for those futures that failed. We want to wait for all them
- // (even if they fail), so better use the latter form.
- ListenableFuture<List<Void>> aggregatedResponses = Futures.successfulAsList(responses.values());
+ private void createKeyPairForPublicKeyInOptionsAndAddToSet(PacketTemplateOptions options,
+ Set<String> generatedSshKeyIds) {
+ logger.debug(">> checking if the key pair already exists...");
- // Key pairs must be cleaned up after all futures completed (even if some failed).
- Futures.addCallback(aggregatedResponses, new FutureCallback<List<Void>>() {
- @Override
- public void onSuccess(List<Void> result) {
- cleanupAutoGeneratedKeyPairs(generatedSshKeyIds);
- }
+ PublicKey userKey = readPublicKey(options.getPublicKey());
+ final String fingerprint = computeFingerprint(userKey);
+ synchronized (CreateSshKeysThenCreateNodes.class) {
+ boolean keyExists = api.sshKeyApi().list().concat().anyMatch(new Predicate<SshKey>() {
@Override
- public void onFailure(Throwable t) {
- cleanupAutoGeneratedKeyPairs(generatedSshKeyIds);
+ public boolean apply(SshKey input) {
+ return input.fingerprint().equals(fingerprint);
}
+ });
+
+ if (!keyExists) {
+ logger.debug(">> key pair not found. creating a new key pair %s ...", fingerprint);
+ SshKey newKey = api.sshKeyApi().create(fingerprint, options.getPublicKey());
+ logger.debug(">> key pair created! %s", newKey);
+ generatedSshKeyIds.add(newKey.id());
+ } else {
+ logger.debug(">> key pair found for key %s", fingerprint);
+ }
+ }
+ }
+
+ private static PublicKey readPublicKey(String publicKey) {
+ Iterable<String> parts = Splitter.on(' ').split(publicKey);
+ checkArgument(size(parts) >= 2, "bad format, should be: ssh-rsa AAAAB3...");
+ String type = get(parts, 0);
+
+ try {
+ if ("ssh-rsa".equals(type)) {
+ RSAPublicKeySpec spec = SshKeys.publicKeySpecFromOpenSSH(publicKey);
+ return KeyFactory.getInstance("RSA").generatePublic(spec);
+ } else {
+ throw new IllegalArgumentException("bad format, ssh-rsa is only supported");
+ }
+ } catch (InvalidKeySpecException ex) {
+ throw propagate(ex);
+ } catch (NoSuchAlgorithmException ex) {
+ throw propagate(ex);
+ }
+ }
+
+ private void generateKeyPairAndAddKeyToSet(PacketTemplateOptions options, Set<String> generatedSshKeyIds,
+ String prefix) {
+ logger.debug(">> creating default keypair for node...");
+
+ Map<String, String> defaultKeys = keyGenerator.get();
+
+ SshKey sshKey = api.sshKeyApi().create(namingConvention.create().uniqueNameForGroup(prefix),
+ defaultKeys.get("public"));
+ generatedSshKeyIds.add(sshKey.id());
+ logger.debug(">> keypair created! %s", sshKey);
- private void cleanupAutoGeneratedKeyPairs(Set<String> generatedSshKeyIds) {
- logger.debug(">> cleaning up auto-generated key pairs...");
- for (String sshKeyId : generatedSshKeyIds) {
- try {
- api.sshKeyApi().delete(sshKeyId);
- } catch (Exception ex) {
- logger.warn(">> could not delete key pair %s: %s", sshKeyId, ex.getMessage());
- }
- }
+ // If a private key has not been explicitly set, configure the generated
+ // one
+ if (Strings.isNullOrEmpty(options.getLoginPrivateKey())) {
+ options.overrideLoginPrivateKey(defaultKeys.get("private"));
+ }
+ }
+
+ private void registerAutoGeneratedKeyPairCleanupCallbacks(Map<?, ListenableFuture<Void>> responses,
+ final Set<String> generatedSshKeyIds) {
+ // The Futures.allAsList fails immediately if some of the futures fail.
+ // The Futures.successfulAsList, however,
+ // returns a list containing the results or 'null' for those futures that
+ // failed. We want to wait for all them
+ // (even if they fail), so better use the latter form.
+ ListenableFuture<List<Void>> aggregatedResponses = Futures.successfulAsList(responses.values());
+
+ // Key pairs must be cleaned up after all futures completed (even if some
+ // failed).
+ Futures.addCallback(aggregatedResponses, new FutureCallback<List<Void>>() {
+ @Override
+ public void onSuccess(List<Void> result) {
+ cleanupAutoGeneratedKeyPairs(generatedSshKeyIds);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ cleanupAutoGeneratedKeyPairs(generatedSshKeyIds);
+ }
+
+ private void cleanupAutoGeneratedKeyPairs(Set<String> generatedSshKeyIds) {
+ logger.debug(">> cleaning up auto-generated key pairs...");
+ for (String sshKeyId : generatedSshKeyIds) {
+ try {
+ api.sshKeyApi().delete(sshKeyId);
+ } catch (Exception ex) {
+ logger.warn(">> could not delete key pair %s: %s", sshKeyId, ex.getMessage());
+ }
}
- }, userExecutor);
- }
-
- private static String computeFingerprint(PublicKey key) {
- if (key instanceof RSAPublicKey) {
- RSAPublicKey rsaKey = (RSAPublicKey) key;
- return SshKeys.fingerprint(rsaKey.getPublicExponent(), rsaKey.getModulus());
- } else {
- throw new IllegalArgumentException("Only RSA keys are supported");
- }
- }
+ }
+ }, userExecutor);
+ }
+
+ private static String computeFingerprint(PublicKey key) {
+ if (key instanceof RSAPublicKey) {
+ RSAPublicKey rsaKey = (RSAPublicKey) key;
+ return SshKeys.fingerprint(rsaKey.getPublicExponent(), rsaKey.getModulus());
+ } else {
+ throw new IllegalArgumentException("Only RSA keys are supported");
+ }
+ }
}