/**
* DeployMan # Thomas Uhrig (Stuttgart, 2014) # www.tuhrig.de
*/
package de.tuhrig.deployman.ssh;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import de.tuhrig.deployman.aws.Ec2;
import static de.tuhrig.deployman.DeployMan.*;
/**
* @author tuhrig
*/
public class SshClient {
private static final String CLOUD_INIT_LOCATION = "/var/lib/cloud/instance/user-data.txt"; //$NON-NLS-1$
private static final String SSH_USER = "ubuntu"; //$NON-NLS-1$
private static final Integer SSH_PORT = 22;
private static final Integer SESSION_CACHE_SIZE = 10;
private static final Integer SESSION_CACHE_TIME = 10;
/**
* A Guave Cache which holds _open_ SSH session. This makes all SSH operations much faster, since
* the connection can be reused. The cache has a limited size and all sessions have a limited life
* time. Sessions will be closed on removal.
*/
private static final Cache<String, Session> sessions = CacheBuilder.newBuilder()
.maximumSize(SESSION_CACHE_SIZE).expireAfterWrite(SESSION_CACHE_TIME, TimeUnit.MINUTES)
.removalListener(new RemovalListener<String, Session>() {
@Override
public void onRemoval(RemovalNotification<String, Session> event) {
event.getValue().disconnect();
}
}).build();
public String getCloutInitScript(String instanceId) {
try {
return runCommand(instanceId, "sudo cat " + CLOUD_INIT_LOCATION); //$NON-NLS-1$;
} catch (JSchException | IOException e) {
return "error on getting cloud init script: " + e.getMessage(); //$NON-NLS-1$
}
}
public String runCommand(String instanceId, String command) throws JSchException, IOException {
JSch jsch = getSshClient();
String hostName = new Ec2().getHostNameOfInstance(instanceId);
Session session = getSession(jsch, hostName);
try {
String result = runCommand(session, command);
return result.trim();
} catch (JSchException e) {
removeSession(hostName);
throw e;
}
}
private void removeSession(String hostName) {
sessions.invalidate(hostName);
}
public void openSshShell(String instanceIndex) {
console.write("Connecting..."); //$NON-NLS-1$
try {
JSch jsch = getSshClient();
String hostName = new Ec2().getHostNameOfInstance(instanceIndex);
console.write("Host " + hostName); //$NON-NLS-1$
console.write("Type 'exit' to quite"); //$NON-NLS-1$
Session session = getSession(jsch, hostName);
while (true) {
String command = readUserInput();
if (command.equals("exit")) //$NON-NLS-1$
break;
String result = runCommand(session, command);
console.write(result);
}
} catch (Exception e) {
console.write("Cannot open SSH session"); //$NON-NLS-1$
e.printStackTrace();
}
}
public String readUserInput() throws IOException {
System.out.print("$: "); //$NON-NLS-1$
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
return input.readLine();
}
private String runCommand(Session session, String command) throws IOException, JSchException {
Channel channel = session.openChannel("exec"); //$NON-NLS-1$
((ChannelExec) channel).setCommand(command);
channel.connect();
InputStream inputStream = channel.getInputStream();
String result = IOUtils.toString(inputStream);
IOUtils.closeQuietly(inputStream);
channel.disconnect();
return result;
}
/**
* Returns an SSH session for the given host name. If a session for the host already exists, the
* cached session is returned. Otherwise, a new session will be created (and stored in the cache).
*/
private Session getSession(JSch jsch, String hostName) throws JSchException {
// return the (open) session if it is already present
Session session = sessions.getIfPresent(hostName);
if (session != null)
return session;
// otherwise, make a new session and add it to the cache
return createAndCacheSession(jsch, hostName);
}
/**
* Creates a new SSH session and stores it in the session cache.
*/
private Session createAndCacheSession(JSch jsch, String hostName) throws JSchException {
Session session = jsch.getSession(SSH_USER, hostName, SSH_PORT);
session.connect();
sessions.put(hostName, session);
return session;
}
/**
* Returns a new SSH client with the configured SSH key. This client can be used to create a new
* SSH session.
*/
private JSch getSshClient() throws JSchException {
JSch jsch = new JSch();
jsch.addIdentity(getUserProperty(SSH_KEY));
JSch.setConfig("StrictHostKeyChecking", "no"); //$NON-NLS-1$ //$NON-NLS-2$
return jsch;
}
}