はじめに
Javaでリモート接続してコマンド実行したり、リモート環境とのファイル転送をしたい用途があったので、ライブラリを使って試してみました。
使用したライブラリ
JSch
基本的な使い方
リモート接続して下記を実施する方法を紹介します。
- コマンド実行
- ファイル転送
接続方法
共通の接続方法についてです。
sshのコマンドとしては下記と同等です。
ssh -p 22 root@192.168.1.1
String user = "root";
String host = "192.168.1.1";
String port = "22";
JSch jsch = new JSch();
Session session = jsch.getSession(user, host, port);
session.setConfig("StrictHostKeyChecking", "no");
session.setPassword(password);
session.connect();
上記はknown_hosts
ファイルを使用しない方法であり、指定する場合は
session.setConfig("StrictHostKeyChecking", "no");
を使用せず下記を使用してください。
jsch.setKnownHosts(".ssh/known_hosts");
コマンド実行
リモート接続してコマンド実行する。
sshのコマンドとしては下記と同等です。
ssh -p 22 root@192.168.1.1 ls
String command = "ls";
ChannelExec channel = (ChannelExec) session.openChannel("exec");
channel.setCommand(command);
channel.connect();
ファイルアップロード
リモート先にファイルをアップロードする。
scpのコマンドとしては下記と同等です。
scp -p 22 test.txt root@192.168.1.1:/tmp/
String fileName = "test.txt";
String dir = "/tmp/";
FileInputStream fileStream = new FileInputStream(fileName);
ChannelSftp channel = (ChannelSftp) session.openChannel("sftp");
channel.connect();
channel.cd(dir);
channel.put(fileStream, fileName);
ファイルダウンロード
リモート先のファイルをダウンロードする。
scpのコマンドとしては下記と同等です。
scp -p 22 root@192.168.1.1:/tmp/test.txt ./tmp/
String remoteFilePath = "/tmp/test.txt";
String localPath = "tmp/";
ChannelSftp channel = (ChannelSftp) session.openChannel("sftp");
channel.connect();
channel.get(remoteFilePath, localPath);
実践編
実装
設定情報
public class RemoteConfig {
protected String host;
protected int port;
protected String user;
protected String password;
public RemoteConfig() {
this.host = "192.168.1.1";
this.port = 22;
this.user = "root";
this.password = "hoge";
}
public String getHost() { return host; }
public int getPort() { return port; }
public String getUser() { return user; }
public String getPassword() { return password; }
}
基底クラス
abstract public class RemoteBee implements Closeable {
protected Session session = null;
public RemoteBee(RemoteConfig connectionSetting) {
connectSession(new JSch(), connectionSetting);
}
/**
* 接続を開始.
*
* @param connectionSetting 接続設定
*/
protected void connectSession(JSch jsch, RemoteConfig connectionSetting) {
try {
this.session = jsch.getSession(
connectionSetting.getUser(),
connectionSetting.getHost(),
connectionSetting.getPort()
);
this.session.setConfig("StrictHostKeyChecking", "no");
this.session.setPassword(connectionSetting.getPassword());
this.session.connect();
} catch (JSchException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public RemoteData execute(RemoteData remoteData) {
try {
openChannel();
return exec(remoteData);
} finally {
closeChannel();
}
}
abstract protected RemoteData exec(RemoteData remoteData);
/**
* 接続.
*/
abstract protected void openChannel();
/**
* 接続を閉じる
*/
protected void closeChannel() {
getChannel().disconnect();
}
abstract protected Channel getChannel();
/**
* 接続を閉じる.
*/
@Override
public void close() {
session.disconnect();
}
}
Sshコマンド実行
public class ShellRemoteData extends RemoteData {
protected String command;
protected List<String> resultMessage = new ArrayList<>();
protected List<String> errorMessage = new ArrayList<>();;
protected Boolean isSuccess = false;
public ShellRemoteData(String command) {
this.command = command;
}
public void addResultMessage(String message) {
this.resultMessage.add(message);
}
public void addErrorMessage(String message) {
this.errorMessage.add(message);
}
public void setSuccess(Boolean success) {
isSuccess = success;
}
public String getCommand() {
return command;
}
public List<String> getResultMessage() {
return resultMessage;
}
public List<String> getErrorMessage() {
return errorMessage;
}
public Boolean getSuccess() {
return isSuccess;
}
}
public class ShellRemote extends RemoteBee {
protected ChannelExec channel = null;
public ShellRemote(RemoteConfig connectionSetting) {
super(connectionSetting);
}
@Override
protected void openChannel() {
try {
this.channel = (ChannelExec) this.session.openChannel("exec");
} catch (JSchException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
@Override
protected ShellRemoteData exec(RemoteData remoteData) {
ShellRemoteData shellRemoteData = (ShellRemoteData) remoteData;
try {
this.channel.setCommand(shellRemoteData.getCommand());
this.channel.connect();
try (BufferedReader errorReader = new BufferedReader(new InputStreamReader(channel.getErrStream()));
BufferedReader echoReader = new BufferedReader(new InputStreamReader(channel.getInputStream()))
) {
String line;
// 実行結果
while ((line = echoReader.readLine()) != null) {
shellRemoteData.addResultMessage(line);
}
// 実行時のエラー結果
while ((line = errorReader.readLine()) != null) {
shellRemoteData.addErrorMessage(line);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
// コマンドの戻り値を取得する
int returnCode = channel.getExitStatus();
if (returnCode == 0) {
System.out.println("====== COMMAND SUCCESS ======");
} else {
System.out.println("====== COMMAND ERROR ======");
}
shellRemoteData.setSuccess(returnCode == 0);
} catch (JSchException e) {
throw new RuntimeException(e);
}
return shellRemoteData;
}
@Override
protected ChannelExec getChannel() {
return channel;
}
}
ファイルの転送
public class ScpRemoteData extends RemoteData {
protected String localPath;
protected String fileName;
protected String remotePath;
public ScpRemoteData(String localPath, String fileName, String remotePath) {
this.localPath = localPath;
this.fileName = fileName;
this.remotePath = remotePath;
}
public FileInputStream getFile() throws FileNotFoundException {
return new FileInputStream(localPath + fileName);
}
public String getRemoteFile() {
return remotePath + fileName;
}
public String getLocalPath() { return localPath; }
public String getFileName() { return fileName; }
public String getRemotePath() { return remotePath; }
}
アップロードする
public class UploadRemote extends RemoteBee {
protected ChannelSftp channel = null;
public UploadRemote(RemoteConfig connectionSetting) {
super(connectionSetting);
}
@Override
protected void openChannel() {
try {
this.channel = (ChannelSftp) this.session.openChannel("sftp");
} catch (JSchException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
@Override
protected ChannelSftp getChannel() {
return channel;
}
@Override
protected ScpRemoteData exec(RemoteData remoteData) {
ScpRemoteData scpRemoteData = (ScpRemoteData) remoteData;
try {
this.channel.connect();
this.channel.cd(scpRemoteData.getRemotePath());
this.channel.put(scpRemoteData.getFile(), scpRemoteData.getFileName());
} catch (SftpException | JSchException | FileNotFoundException e) {
throw new RuntimeException(e);
}
return scpRemoteData;
}
}
ダウンロードする
public class DownloadRemote extends RemoteBee {
protected ChannelSftp channel = null;
public DownloadRemote(RemoteConfig connectionSetting) {
super(connectionSetting);
}
@Override
protected void openChannel() {
try {
this.channel = (ChannelSftp) this.session.openChannel("sftp");
} catch (JSchException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
@Override
protected ChannelSftp getChannel() {
return channel;
}
@Override
protected ScpRemoteData exec(RemoteData remoteData) {
ScpRemoteData scpRemoteData = (ScpRemoteData) remoteData;
try {
this.channel.connect();
this.channel.get(scpRemoteData.getRemotePath(), scpRemoteData.getLocalPath());
} catch (SftpException | JSchException e) {
throw new RuntimeException(e);
}
return scpRemoteData;
}
}
使い方
public class Main {
public static void main(String[] args) {
RemoteConfig remoteConfig = new RemoteConfig();
try (UploadRemote uploadRemote = new UploadRemote(remoteConfig)) {
// ダンプするファイルをUPする
uploadRemote.execute(new ScpRemoteData("tmp/", "testBefore.dump" ,"/tmp/"));
}
try (ShellRemote shellRemote = new ShellRemote(remoteConfig)) {
// テスト前の DB をリストアする
shellRemote.execute(new ShellRemoteData("createdb testdb"));
shellRemote.execute(new ShellRemoteData("psql -U postgres testdb < testBefore.dump"));
// テストを実施する
shellRemote.execute(new ShellRemoteData("psql -U postgres -c \"update db SET column_value = 1;\""));
// テスト後の DB をダンプする
shellRemote.execute(new ShellRemoteData("pg_dump testdb > /tmp/testAfter.dump"));
}
try (DownloadRemote downloadRemote = new DownloadRemote(remoteConfig)) {
// ダンプしたファイルをDLする
downloadRemote.execute(new ScpRemoteData("tmp/", "testAfter.dump" ,"/tmp/"));
}
}
}
感想
今回JavaからSsh接続したかった目的は、WebアプリのE2Eテストでアプリの状態をテスト前に戻したいという用途でした。
E2Eテストは、JavaのSeleniumで作られたものがあったのですが、アプリが動作する環境をテスト前に戻すために、
シェルコマンド(.sh
ファイル)が別に実装されていたため、シェルコマンドをJavaとは別に実行しないといけないなど冗長さがありました。
全部Javaでできないかなーということで今回のライブラリを試してみました。
Javaからsshコマンドを実行したいことは多くはないですが、覚えておくと便利だなって思いました。
備考
Javaは久しぶりだったのできれいに作れた気がしないので、細かな実装部分に関してはご容赦ください。
作りがおかしいところがあればご指摘お願いします。
参考