package core.aws.util;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpATTRS;
import com.jcraft.jsch.SftpException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileVisitResult;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Properties;
import static java.nio.file.Files.isDirectory;
import static java.nio.file.Files.walkFileTree;
/**
* @author neo
*/
public final class SSH implements Closeable {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final Logger messageLogger = LoggerFactory.getLogger("message");
private final String host;
private final String user;
private final Path privateKey;
private Session session;
public SSH(String host, String user, Path privateKey) {
this.host = host;
this.user = user;
this.privateKey = privateKey;
}
@Override
public void close() throws IOException {
session.disconnect();
}
public void executeCommands(String... commands) throws JSchException, IOException, InterruptedException {
connectIfNot();
for (String command : commands) {
executeCommand(command);
}
}
public void put(Path localPath, String remotePath) throws JSchException, SftpException {
connectIfNot();
ChannelSftp channel = (ChannelSftp) session.openChannel("sftp");
try {
channel.connect();
logger.info("sftp put, from={}, to={}", localPath, remotePath);
channel.put(new ByteArrayInputStream(Files.bytes(localPath)), remotePath);
} finally {
channel.disconnect();
}
}
// copy all files recursively from local dir to remote dir, e.g. copy local env/packages to /opt/packages
public void uploadDir(final Path localDir, final String remoteDir) {
Asserts.isTrue(isDirectory(localDir), "localDir must be directory, localDir={}", localDir);
ChannelSftp channel = null;
try {
connectIfNot();
channel = (ChannelSftp) session.openChannel("sftp");
channel.connect();
walkFileTree(localDir, new PutFileVisitor(localDir, remoteDir, channel));
} catch (JSchException | IOException e) {
throw new SSHException(e);
} finally {
if (channel != null)
channel.disconnect();
}
}
private void executeCommand(String command) throws JSchException, IOException, InterruptedException {
connectIfNot();
Channel channel = session.openChannel("exec");
try {
((ChannelExec) channel).setCommand(command);
((ChannelExec) channel).setErrStream(System.err);
((ChannelExec) channel).setPty(true);
((ChannelExec) channel).setPtyType("vt100");
channel.setInputStream(null);
channel.setOutputStream(System.out);
InputStream in = channel.getInputStream();
logger.info("ssh exec command => {}", command);
channel.connect();
byte[] buffer = new byte[1024];
while (true) {
while (in.available() > 0) {
int i = in.read(buffer, 0, 1024);
if (i < 0) break;
messageLogger.info(new String(buffer, 0, i, Charsets.UTF_8));
}
if (channel.isClosed()) {
logger.info("ssh exec exit status => " + channel.getExitStatus());
break;
}
Thread.sleep(1000);
}
if (channel.getExitStatus() != 0) {
throw new JSchException("failed to run command, command=" + command);
}
} finally {
channel.disconnect();
}
}
private void connectIfNot() throws JSchException {
if (session == null) {
JSch jsch = new JSch();
jsch.addIdentity(privateKey.toAbsolutePath().toString());
session = jsch.getSession(user, host, 22);
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
logger.info("ssh connect to {}", host);
session.connect();
}
}
public static class SSHException extends RuntimeException {
private static final long serialVersionUID = 3248580184982451829L;
public SSHException(Throwable cause) {
super(cause);
}
}
private class PutFileVisitor extends SimpleFileVisitor<Path> {
private final String remoteDir;
private final Path localDir;
private final ChannelSftp channel;
PutFileVisitor(Path localDir, String remoteDir, ChannelSftp channel) {
this.remoteDir = remoteDir;
this.localDir = localDir;
this.channel = channel;
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attributes) throws IOException {
try {
String targetDir = remoteDir + "/" + toLinuxPath(localDir.relativize(dir));
if (!remoteDirExists(targetDir)) {
logger.info("sftp mkdir, remoteDir={}", targetDir);
channel.mkdir(targetDir);
}
return FileVisitResult.CONTINUE;
} catch (SftpException e) {
throw new IOException(e);
}
}
private boolean remoteDirExists(String remoteDir) {
try {
SftpATTRS stat = channel.stat(remoteDir);
return stat.isDir();
} catch (SftpException e) {
return false;
}
}
private String toLinuxPath(Path path) {
return path.toString().replace("\\", "/"); // convert windows file separator to linux one
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) throws IOException {
try {
String source = file.toAbsolutePath().toString();
String destination = remoteDir + "/" + toLinuxPath(localDir.relativize(file));
logger.info("sftp put, source={}, destination={}", source, destination);
channel.put(source, destination);
return FileVisitResult.CONTINUE;
} catch (SftpException e) {
throw new IOException(e);
}
}
}
}