티스토리 뷰

안녕하세요. 오랜만에 포스팅을 작성하게 되었는데, 오늘은 Java 코드에 SSHUtil을 만들어서 리눅스 서버에 명령어를 실행하고 그 결과 값을 받아와 화면에 출력하는 방법에 대해서 간단한 예제를 통해서 설명해보려고 합니다. 

 

우선, 서버에 SSH로 접속해서 명령어를 실행할 SSHUtil 클래스를 아래와 같이 만들어 줍니다.

/**
 * <pre>
 * SSH Util class
 * </pre>
 *
 * @author toma
 * @version 1.0
 */
public class SSHUtil {

    private static final Logger logger = LoggerFactory.getLogger(SSHUtil.class);
 
 	/**
     * <pre>
     * Execute shell command in remote server via SSH
     * </pre>
     *
     * @param targetHost
     * @param command
     *
     * @return
     *
     * @throws Exception
     */
    public static String executeCommand(TargetHost targetHost, String command) throws RoRoException {
        return executeCommand(targetHost, command, null);
    }
    
    /**
     * <pre>
     * Execute shell command in remote server via SSH
     * </pre>
     *
     * @param targetHost
     * @param command
     * @param pos
     *
     * @return
     *
     * @throws RoRoException
     */
    public static String executeCommand(TargetHost targetHost, String command, PipedOutputStream pos) throws RoRoException {
        Session session = null;
        Channel channel = null;

        StringBuilder str = new StringBuilder();

        BufferedWriter writer = null;

        if (pos != null) {
            writer = new BufferedWriter(new OutputStreamWriter(pos));
        }

        if (targetHost.getUsername().equals("root")) {
            if (command.startsWith("sudo ")) {
                command = command.substring(5);
            } else if (command.startsWith("/usr/bin/sudo ")) {
                command = command.substring(14);
            }
        }

        String cmdMessage = "[" + targetHost.getUsername() + "@" + targetHost.getIpAddress() + " ~]$ " + command;

        logger.debug(cmdMessage + "\n");

        try {
            session = getSessionForTimeout(targetHost);

            channel = session.openChannel("exec");

            ((ChannelExec) channel).setCommand(command);
            channel.setInputStream(null);
            ((ChannelExec) channel).setErrStream(System.err);

            if (command.startsWith("sudo ") || command.startsWith("/usr/bin/sudo ")) {
                ((ChannelExec) channel).setPty(true);
            }

            InputStream in = channel.getInputStream();

            channel.connect();

            byte[] tmp = new byte[4096];
            while (true) {
                while (in.available() > 0) {
                    int i = in.read(tmp, 0, 4096);

                    if (i < 0) {
                        break;
                    }

                    String line = new String(tmp, 0, i);
                    if (writer != null) {
                        writer.write(line);
                        writer.flush();
                    }

                    str.append(new String(tmp, 0, i));
                }

                if (channel.isClosed()) {
                    if (in.available() > 0) {
                        continue;
                    }

                    logger.debug("SSHUtil.executeCommand(\"{}\")'s Exit Status: [{}]", command, channel.getExitStatus());
                    break;
                }

                Thread.sleep(500);
            }
        } catch (Exception e) {
            logger.warn("Unhandled exception occurred while execute command '{}' with '{}' error.", cmdMessage, e.getMessage());
            throw new RoRoException(e.getMessage(), e);
        } finally {
            if (channel != null && channel.isConnected()) {
                channel.disconnect();
            }

            if (session != null && session.isConnected()) {
                session.disconnect();
            }

            if (writer != null) {
                IOUtils.closeQuietly(writer);
            }
        }

        return str.toString();
    }
}

사용자의 입맛에 따라 SSHUtil에는 더욱 많은 작업(예를 들어, 서버의 healthCheck 등)을 구현하셔서 사용해도 됩니다.

 

그리고, 서버의 정보를 받아 TargetHost 컨버팅해서 사용될 클래스를 생성해줍니다. 여기서 TargetHost 클래스가 아닌 서버의 정보를 담고 있는 객체를 따로 만드셔서 사용해도 무방합니다.

/**
 * <pre>
 * 서버의 정보를 담는 POJO 객체
 * </pre>
 *
 * @author toma
 * @version 1.0
 */
public class TargetHost implements Serializable {

	private static final long serialVersionUID = 1L;

	/** Callback 받을 URL */
	private String callback;

	/** 프로비저닝 대상 IP Address */
	private String ipAddress;

	/** 프로비저닝 대상의 SSH Port 번호 */
	private Integer port;

	/** 프로비저닝 대상 Host의 Shell 계정 */
	private String username;

	/** 프로비저닝 대상 Host의 Shell 패스워드 */
	private String password;

	/** is trust Y/N (default : true) */
	private boolean trust = true;

	/** ssh key file */
	private String keyFilePath;

	/** ssh key file */
	private String keyString;

	/**
	 * Convert target host.
	 *
	 * @param server the server
	 * @return the target host
	 */
	public static TargetHost convert(Server server) {
		TargetHost targetHost = new TargetHost();
		targetHost.setIpAddress(server.getIpAddress());
		targetHost.setPort(server.getPort());
		targetHost.setUsername(server.getUsername());
        targetHost.setPassword(server.getPassword());
		// targetHost.setPassword(GeneralCipherUtil.decrypt(server.getPassword()));
		targetHost.setKeyFilePath(server.getKeyFilePath());
		targetHost.setKeyString(server.getKeyFileString());

		return targetHost;
	}

	/**
	 * Gets callback.
	 *
	 * @return the callback
	 */
	public String getCallback() {
		return callback;
	}

	/**
	 * Sets callback.
	 *
	 * @param callback the callback to set
	 */
	public void setCallback(String callback) {
		this.callback = callback;
	}

	/**
	 * Gets ip address.
	 *
	 * @return the ip address
	 */
	public String getIpAddress() {
		return ipAddress;
	}

	/**
	 * Sets ip address.
	 *
	 * @param ipAddress the ip address
	 */
	public void setIpAddress(String ipAddress) {
		this.ipAddress = ipAddress;
	}

	/**
	 * Gets port.
	 *
	 * @return the port
	 */
	public Integer getPort() {
		return port;
	}

	/**
	 * Sets port.
	 *
	 * @param port the port to set
	 */
	public void setPort(Integer port) {
		this.port = port;
	}

	/**
	 * Gets username.
	 *
	 * @return the username
	 */
	public String getUsername() {
		return username;
	}

	/**
	 * Sets username.
	 *
	 * @param username the username to set
	 */
	public void setUsername(String username) {
		this.username = username;
	}

	/**
	 * Gets password.
	 *
	 * @return the password
	 */
	public String getPassword() {
		return password;
	}

	/**
	 * Sets password.
	 *
	 * @param password the password to set
	 */
	public void setPassword(String password) {
		this.password = password;
	}

	/**
	 * Is trust boolean.
	 *
	 * @return the trust
	 */
	public boolean isTrust() {
		return trust;
	}

	/**
	 * Sets trust.
	 *
	 * @param trust the trust to set
	 */
	public void setTrust(boolean trust) {
		this.trust = trust;
	}

	/**
	 * Gets key file path.
	 *
	 * @return the key file path
	 */
	public String getKeyFilePath() {
		return keyFilePath;
	}

	/**
	 * Sets key file path.
	 *
	 * @param keyFilePath the key file path
	 */
	public void setKeyFilePath(String keyFilePath) {
		this.keyFilePath = keyFilePath;
	}

	/**
	 * Gets key string.
	 *
	 * @return the key string
	 */
	public String getKeyString() {
		return keyString;
	}

	/**
	 * Sets key string.
	 *
	 * @param keyString the key string
	 */
	public void setKeyString(String keyString) {
		this.keyString = keyString;
	}

	@Override
	public String toString() {
		return "[callback=" + callback + ", ipAddress=" + ipAddress + ", port=" + port + ", username=" + username + ", password="
				+ password + ", trust=" + trust + ", keyFilePath=" + keyFilePath + ", keyString=" + keyString + "]";
	}

 

우선, 테스트를 위한 준비를 마쳤으며, 메인 클래스에서 SSHUtil 사용을 통해서 리눅스 서버의 명령어 수행하는 방법에 대해서 확인해보도록 하겠습니다. 명령어를 String 타입으로 정의해서 사용하도록 하겠습니다.

/**
 * <pre>
 *
 * </pre>
 *
 * @author toma
 * @version 2.0.0
 */
@Slf4j
public class CommandTest {


    public static void main(String[] args) {
        // 서버 접속을 위한 간략한 정보
        Server server = new Server();
        server.setIpAddress("localhost"); // ip
        server.setPort(22);				  // port
        server.setUsername("toma");		  // username
        server.setPassword("toma");	  // password

        // ls -al 명령어
        String lsCommand = "ls -al";

        String lsResult = SSHUtil.executeCommand(TargetHost.convert(server), lsCommand);
        log.debug(":+:+:+: " + lsCommand + " Result : [{}]", lsResult);

        // cat test.txt 명령어
        String catCommand = "cat test.txt";

        String catResult = SSHUtil.executeCommand(TargetHost.convert(server), catCommand);
        log.debug(":+:+:+: " + catCommand + " Result : [{}]", catResult);

        // java -version 명령어
        String javaHome = "/usr/lib/jvm/java-1.8.0";
        String javaCommand = javaHome + "/bin/" + "java -version 2>&1 | head -n 1 | awk -F '\"' '{print $2}'";

        String javaResult = SSHUtil.executeCommand(TargetHost.convert(server), javaCommand);
        log.debug(":+:+:+: " + javaCommand + " Result : [{}]", javaResult);

        // ps -ef | grep java 명령어
        String psCommand = "ps -ef | grep java";

        String psResult = SSHUtil.executeCommand(TargetHost.convert(server), psCommand);
        log.debug(":+:+:+: " + psCommand + " Result : [{}]", psResult);

        // echo "Test!" > abc.txt 명령어로 파일 생성
        String echoCommand = "echo \"Test!\" > abc.txt";

        String echoResult = SSHUtil.executeCommand(TargetHost.convert(server), echoCommand);
        log.debug(":+:+:+: " + echoCommand + " Result : [{}]", echoResult);
    }
}

위의 명령어를 실행한 결과들이 debug로 화면에 출력되는 것을 확인 하실 수 있습니다. 리눅스 서버에는 다양한 종류의 명령어 들이 있는데, 하나씩 하나씩 테스트 해보는 것도 좋을 것 같습니다.

 

그럼 오늘 SSHUtil을 통해 직접 SSH로 붙어서 명령어를 사용하고 그 결과 값을 화면에 출력하는 예제를 살펴봤고, 시간이 나게 되면 Apache Commons에서 제공하는 CommandLine에 대해서도 포스팅 해보도록 하겠습니다. 이만 포스팅을 마치도록 하겠습니다.