0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ラクスAdvent Calendar 2022

Day 10

実践JavaでSSH接続

Last updated at Posted at 2022-12-10

はじめに

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は久しぶりだったのできれいに作れた気がしないので、細かな実装部分に関してはご容赦ください。
作りがおかしいところがあればご指摘お願いします。

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?