DevOps自动化平台开发之 Shell脚本执行的封装

 基础知识

基于如下技术栈开发DevOps平台

Spring Boot

Shell

Ansible

Git

Gitlab

Docker

K8S

Vue

 1、spring boot starter的封装使用

2、Shell脚本的编写

3、Ansible 脚本的编写

4、Docker 的使用与封装设计

本篇介绍如何使用Java封装Linux命令和Shell脚本的使用

将其设计成spring boot starter

maven依赖pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.9</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.devops</groupId>
    <artifactId>ssh-client-pool-spring-boot-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>ssh-client-pool-spring-boot-starter</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.hierynomus</groupId>
            <artifactId>sshj</artifactId>
            <version>0.26.0</version>
        </dependency>
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15on</artifactId>
            <version>1.60</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.uuid</groupId>
            <artifactId>java-uuid-generator</artifactId>
            <version>3.1.4</version>
        </dependency>
        <dependency>
            <groupId>net.sf.expectit</groupId>
            <artifactId>expectit-core</artifactId>
            <version>0.9.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.8.0</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

具体的封装代码: 

package com.devops.ssh.autoconfigure;

import com.devops.ssh.pool.SshClientPoolConfig;
import com.devops.ssh.pool.SshClientsPool;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author Gary
 */
@Configuration
@EnableConfigurationProperties(SshClientPoolProperties.class)
public class SshClientPoolAutoConfiguration {

	private final SshClientPoolProperties properties;

	public SshClientPoolAutoConfiguration(SshClientPoolProperties properties) {
		this.properties = properties;
	}

	@Bean
	@ConditionalOnMissingBean(SshClientsPool.class)
	SshClientsPool sshClientsPool() {
		return new SshClientsPool(sshClientPoolConfig());
	}

	SshClientPoolConfig sshClientPoolConfig() {
		SshClientPoolConfig poolConfig = new SshClientPoolConfig(properties.getMaxActive()
				,properties.getMaxIdle()
				,properties.getIdleTime()
				,properties.getMaxWait());
		if(properties.getSshj()!=null) {
			poolConfig.setServerCommandPromotRegex(properties.getSshj().getServerCommandPromotRegex());
		}
		if (properties.getSshClientImplClass()!=null) {
			try {
				poolConfig.setSshClientImplClass(Class.forName(properties.getSshClientImplClass()));
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
		}
		return poolConfig;
	}
}

package com.devops.ssh.autoconfigure;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("devops.ssh-client-pool")
public class SshClientPoolProperties {
	
	/**
	 * Max number of "idle" connections in the pool. Use a negative value to indicate
	 * an unlimited number of idle connections.
	 */
	private int maxIdle = 20;

	/**
	 * 
	 */
	private int idleTime = 120*1000;

	/**
	 * Max number of connections that can be allocated by the pool at a given time.
	 * Use a negative value for no limit.
	 */
	private int maxActive = 20;

	/**
	 * Maximum amount of time (in milliseconds) a connection allocation should block
	 * before throwing an exception when the pool is exhausted. Use a negative value
	 * to block indefinitely.
	 */
	private int maxWait = 120*1000;
	
	private String sshClientImplClass = "com.devops.ssh.SshClientSSHJ";
	
	private SshClientProperites sshj;
	
	
	public int getMaxIdle() {
		return maxIdle;
	}


	public void setMaxIdle(int maxIdle) {
		this.maxIdle = maxIdle;
	}



	public int getIdleTime() {
		return idleTime;
	}



	public void setIdleTime(int idleTime) {
		this.idleTime = idleTime;
	}



	public int getMaxActive() {
		return maxActive;
	}



	public void setMaxActive(int maxActive) {
		this.maxActive = maxActive;
	}



	public int getMaxWait() {
		return maxWait;
	}



	public void setMaxWait(int maxWait) {
		this.maxWait = maxWait;
	}



	public String getSshClientImplClass() {
		return sshClientImplClass;
	}



	public void setSshClientImplClass(String sshClientImplClass) {
		this.sshClientImplClass = sshClientImplClass;
	}



	public SshClientProperites getSshj() {
		return sshj;
	}



	public void setSshj(SshClientProperites sshj) {
		this.sshj = sshj;
	}



	public static class SshClientProperites{
		private String serverCommandPromotRegex;

		public String getServerCommandPromotRegex() {
			return serverCommandPromotRegex;
		}

		public void setServerCommandPromotRegex(String serverCommandPromotRegex) {
			this.serverCommandPromotRegex = serverCommandPromotRegex;
		}
		
	}
	
}

package com.devops.ssh.exception;

/**
 * Ssh auth failed
 * @author Gary
 *
 */
public class AuthException extends SshException{

	public AuthException(String message) {
		this(message, null);
	}
	
	public AuthException(String message, Throwable error) {
		super(message, error);
	}

	/**
	 * 
	 */
	private static final long serialVersionUID = -3961786667342327L;

}
package com.devops.ssh.exception;

/**
 * The ssh connection is disconnected
 * @author Gary
 *
 */
public class LostConnectionException extends SshException{

	
	private static final long serialVersionUID = -3961870786667342727L;

	public LostConnectionException(String message) {
		this(message, null);
	}
	
	public LostConnectionException(String message, Throwable error) {
		super(message, error);
	}
}
package com.devops.ssh.exception;

public class SshException extends Exception{

	/**
	 * 
	 */
	private static final long serialVersionUID = 2052615275027564490L;
	
	public SshException(String message, Throwable error) {
		super(message);
		if(error != null) {
			initCause(error);
		}
	}
	
	public SshException(String message) {
		this(message, null);
	}
	
}
package com.devops.ssh.exception;


/**
 * Timeout Exception
 * @author Gary
 *
 */
public class TimeoutException extends SshException {

	public TimeoutException(String message) {
		this(message, null);
	}

	public TimeoutException(String message, Throwable error) {
		super(message, error);
	}

	/**
	 *
	 */
	private static final long serialVersionUID = -39618386667342727L;

}
package com.devops.ssh.pool;


import com.devops.ssh.SshClient;
import com.devops.ssh.SshClientSSHJ;
import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;


/**
 *
 * The configuration of SshClientPool library
 * <p>SshClientPoolConfig is a subclass of GenericKeyedObjectPoolConfig to control the pool behavior
 * <p>Also, you can replace the build-in {@link SshClient} implementation by {@link SshClientPoolConfig#setSshClientImplClass(Class)} if you want
 *
 * @author Gary
 */
public class SshClientPoolConfig extends GenericKeyedObjectPoolConfig<SshClientWrapper>{

	private Class<?> sshClientImplClass;

	private String serverCommandPromotRegex;

	public SshClientPoolConfig() {
		super();
	}

	/**
	 * quick way to create SshClientPoolConfig
	 * set TestOnBorrow to true
	 * set TestOnReturn to true
	 * set TestWhileIdle to true
	 * set JmxEnabled to false
	 * @param maxActive maxTotalPerKey
	 * @param maxIdle maxIdlePerKey
	 * @param idleTime idle time
	 * @param maxWaitTime maxWaitMillis
	 */
	public SshClientPoolConfig(int maxActive, int maxIdle, long idleTime,  long maxWaitTime){
		this.setMaxTotalPerKey(maxActive);
		this.setMaxIdlePerKey(maxIdle);
		this.setMaxWaitMillis(maxWaitTime);
		this.setBlockWhenExhausted(true);
		this.setMinEvictableIdleTimeMillis(idleTime);
		this.setTimeBetweenEvictionRunsMillis(idleTime);
		this.setTestOnBorrow(true);
		this.setTestOnReturn(true);
		this.setTestWhileIdle(true);
		this.setJmxEnabled(false);
	}

	public Class<?> getSshClientImplClass() {
		return sshClientImplClass;
	}

	/**
	 * replace the build-in {@link SshClient} by {@link SshClientPoolConfig#setSshClientImplClass(Class)}
	 * @param sshClientImplClass the implementation of {@link SshClient}
	 */
	public void setSshClientImplClass(Class<?> sshClientImplClass) {
		this.sshClientImplClass = sshClientImplClass;
	}

	/**
	 *
	 * @return regex string used to match promot from server
	 */
	public String getServerCommandPromotRegex() {
		return serverCommandPromotRegex;
	}

	/**
	 * see {@link SshClientSSHJ#setCommandPromotRegexStr(String)}
	 * @param serverCommandPromotRegex regex string used to match promot from server
	 */
	public void setServerCommandPromotRegex(String serverCommandPromotRegex) {
		this.serverCommandPromotRegex = serverCommandPromotRegex;
	}


}
package com.devops.ssh.pool;

import com.devops.ssh.*;
import com.devops.ssh.exception.SshException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.UUID;

/**
 * A wrapper class of {@link SshClient} used by {@link SshClientsPool}
 *
 * @author Gary
 *
 */
public class SshClientWrapper implements SshClientEventListener {

	private final static Logger logger = LoggerFactory.getLogger(SshClientWrapper.class);

	private String id;

	private SshClient client;

	SshClientEventListener listener;

	SshClientConfig config;

	public String getId() {
		return this.id;
	}

	public void setListener(SshClientEventListener listener) {
		this.listener = listener;
	}

	public SshClientConfig getConfig() {
		return this.config;
	}

	public SshClientWrapper(SshClientConfig config, SshClientPoolConfig poolConfig) {
		this.id = UUID.randomUUID().toString();
		this.config = config;
		this.client = SshClientFactory.newInstance(config, poolConfig);
	}

	public SshClientWrapper setEventListener(SshClientEventListener listener) {
		this.listener = listener;
		this.client.setEventListener(this);
		return this;
	}

	public SshClientWrapper connect(int timeoutInSeconds) throws SshException {
		client.connect(timeoutInSeconds);
		return this;
	}

	public SshClientWrapper auth() throws SshException{
		if(null!=this.config.getPassword() && this.config.getPassword().length()>0) {
			client.authPassword();
		}else if(null!=this.config.getPrivateKeyPath() && this.config.getPrivateKeyPath().length()>0) {
			client.authPublickey();
		}else {
			client.authPublickey();
		}
		return this;
	}


	public SshClientWrapper startSession() throws SshException{
		client.startSession(true);
		return this;
	}


	public SshResponse executeCommand(String command, int timeoutInSeconds){
		SshResponse response = client.executeCommand(command, timeoutInSeconds);
		return response;
	}

	public void disconnect() {
		client.disconnect();
	}

	@Override
	public boolean equals(Object obj) {
		if(obj instanceof SshClientWrapper){
			return id.equals(((SshClientWrapper)obj).getId());
		}
		return false;
	}

	@Override
	public int hashCode(){
		return id.hashCode();
	}

	public SshClientState getState() {
		return client.getState();
	}

	@Override
	public String toString() {
		return "["+this.id+"|"
					+this.config.getHost()+"|"
					+this.config.getPort()+"|"
					+this.getState()+"]";
	}

	@Override
	public void didExecuteCommand(Object client) {
		this.listener.didExecuteCommand(this);
	}

	@Override
	public void didDisConnected(Object client) {
		this.listener.didDisConnected(this);
	}

	@Override
	public void didConnected(Object client) {
		this.listener.didConnected(this);
	}

}
package com.devops.ssh;


import com.devops.ssh.exception.SshException;

/**
 * Ssh Client used to connect to server instance and execute command. The build-in implementation is {@link SshClientSSHJ}<p>
 *
 * Client can be used in chain mode, {@link SshClient}.{@link #init(SshClientConfig)}.{@link #connect(int)}.{@link #authPassword()}.{@link #startSession(boolean)}.{@link #executeCommand(String, int)}<p>
 *
 * At last, close the client with {@link #disconnect()}
 *
 * <p>Set an {@link SshClientEventListener} with {@link #setEventListener(SshClientEventListener)} to be notified when its event occurs
 * <p>
 * @author Gary
 *
 */
public interface SshClient {

	/**
	 * pass the {@link SshClientConfig} to client
	 * @param config the information used to connect to server
	 * @return SshClient itself
	 */
	public SshClient init(SshClientConfig config);

	/**
	 * connect to server, and timeout if longer than {@code timeoutInSeconds}
	 * @param timeoutInSeconds timeout in seconds
	 * @return SshClient itself
	 * @throws SshException if server is unreachable, usually the host and port is incorrect
	 */
	public SshClient connect(int timeoutInSeconds) throws SshException;

	/**
	 * auth with password
	 * @return SshClient itself
	 * @throws SshException if username or password is incorrect
	 */
	public SshClient authPassword() throws SshException;

	/**
	 * auth with key
	 * @return SshClient itself
	 * @throws SshException if username or public key is incorrect
	 */
	public SshClient authPublickey() throws SshException;

	/**
	 * start session
	 * @param shellMode <tt>true</tt>: communicate with server interactively in session, just like command line
	 * <p><tt>false</tt>: only execute command once in session
	 * @return SshClient itself
	 * @throws SshException when start session failed
	 *
	 */
	public SshClient startSession(boolean shellMode) throws SshException;

	/**
	 *
	 * @param command execute the {@code command} on server instance, and timeout if longer than {@code timeoutInSeconds}.
	 * @param timeoutInSeconds timeout in seconds
	 * @return SshResponse
	 *
	 */
	public SshResponse executeCommand(String command, int timeoutInSeconds);

	/**
	 * set the listener on SshClient
	 * @param listener notify listener when events occur in SshClient
	 * @return SshClient itself
	 */
	public SshClient setEventListener(SshClientEventListener listener);

	/**
	 * disconnect from server
	 */
	public void disconnect();

	/**
	 * state of SshClient
	 *
	 * @return SshClientState the state of ssh client
	 * <p><tt>inited</tt> before {@link #startSession(boolean)} success
	 * <p><tt>connected</tt> after {@link #startSession(boolean)} success
	 * <p><tt>disconnected</tt> after {@link #disconnect()}, or any connection problem occurs
	 */
	public SshClientState getState();

}
package com.devops.ssh;

/**
 *
 * Configuration used by {@link SshClient} to connect to remote server instance
 *
 * @author Gary
 *
 */
public class SshClientConfig {
	private String host;
	private int port;
	private String username;
	private String password;
	private String privateKeyPath;
	private String id;

	/**
	 *
	 * @return host address
	 */
	public String getHost() {
		return host;
	}

	/**
	 * @param host host address, usually the ip address of remote server
	 */
	public void setHost(String host) {
		this.host = host;
	}

	/**
	 *
	 * @return ssh port of the remote server
	 */
	public int getPort() {
		return port;
	}

	/**
	 * @param port ssh port of the remote server
	 */
	public void setPort(int port) {
		this.port = port;
	}

	/**
	 *
	 * @return ssh username of the remote server
	 */
	public String getUsername() {
		return username;
	}

	/**
	 *
	 * @param username ssh username of the remote server
	 */
	public void setUsername(String username) {
		this.username = username;
	}

	/**
	 *
	 * @return ssh password of the remote server
	 */
	public String getPassword() {
		return password;
	}

	/**
	 *
	 * @param password ssh password of the remote server
	 */
	public void setPassword(String password) {
		this.password = password;
	}

	/**
	 * @return ssh local key file path of the remote server
	 */
	public String getPrivateKeyPath() {
		return privateKeyPath;
	}

	/**
	 * @param privateKeyPath local key file path of the remote server
	 */
	public void setPrivateKeyPath(String privateKeyPath) {
		this.privateKeyPath = privateKeyPath;
	}

	/**
	 *
	 * @return id of the config
	 */
	public String getId() {
		return id;
	}

	/**
	 *
	 * @param host           server host address
	 * @param port           server ssh port
	 * @param username       server ssh username
	 * @param password       server ssh password
	 * @param privateKeyPath local security key used to connect to server
	 */
	public SshClientConfig(String host, int port, String username, String password, String privateKeyPath) {
		this.id = host + port + username;
		if (null != password && password.length() > 0) {
			this.id += password;
		}
		if (privateKeyPath != null) {
			this.id += privateKeyPath;
		}
		this.host = host;
		this.port = port;
		this.username = username;
		this.password = password;
		this.privateKeyPath = privateKeyPath;
	}

	public SshClientConfig() {

	}

	@Override
	public boolean equals(Object obj) {
		if (obj instanceof SshClientConfig) {
			return id.equals(((SshClientConfig) obj).getId());
		}
		return false;
	}

	@Override
	public int hashCode() {
		return id.hashCode();
	}

	@Override
	public String toString() {
		return this.id;
	}
}
package com.devops.ssh;

/**
 *
 * Set listener to a SshClient by {@link SshClient#setEventListener(SshClientEventListener)}
 * @author Gary
 *
 */
public interface SshClientEventListener {

	/**
	 * after SshClient finished executing command
	 * @param client the ssh client
	 */
	public void didExecuteCommand(Object client);

	/**
	 * after SshClient disconnnect from the remote server
	 * @param client the ssh client
	 */
	public void didDisConnected(Object client);

	/**
	 * after SshClient start the ssh session
	 * @param client the ssh client
	 */
	public void didConnected(Object client);
}
package com.devops.ssh;


import com.devops.ssh.pool.SshClientPoolConfig;

/**
 *
 * Factory of {@link SshClient} implementation
 * <p> Create a new instance of {@link SshClientSSHJ} with {@link #newInstance(SshClientConfig)}
 * <p> Create a custom implementation of {@link SshClient} with {@link #newInstance(SshClientConfig, SshClientPoolConfig)}
 *
 * @author Gary
 *
 */
public class SshClientFactory {

	/**
	 * Create a new instance of {@link SshClientSSHJ}
	 * @param config ssh connection configuration of the remote server
	 * @return SshClient in inited state
	 */
	public static SshClient newInstance(SshClientConfig config){
		return newInstance(config, null);
	}

	/**
	 * Create a custom implementation of {@link SshClient}
	 * @param config ssh connection configuration of the remote server
	 * @param poolConfig customized configuration
	 * @return SshClient in inited state
	 * @throws RuntimeException if SshClientImplClass in {@code poolConfig} is invalid
	 */
	public static SshClient newInstance(SshClientConfig config, SshClientPoolConfig poolConfig){
		try {
			SshClient client = null;
			if (poolConfig==null || poolConfig.getSshClientImplClass()==null){
				client = new SshClientSSHJ();
			}else {
				client = (SshClient)poolConfig.getSshClientImplClass().newInstance();
			}
			client.init(config);
			if(client instanceof SshClientSSHJ && poolConfig!=null && poolConfig.getServerCommandPromotRegex()!=null) {
				((SshClientSSHJ)client).setCommandPromotRegexStr(poolConfig.getServerCommandPromotRegex());
			}
			return client;
		} catch (InstantiationException e) {
			throw new RuntimeException("new instance failed", e);
		} catch (IllegalAccessException e) {
			throw new RuntimeException("new instance failed", e);
		}
	}

}
package com.devops.ssh;

import com.devops.ssh.exception.AuthException;
import com.devops.ssh.exception.LostConnectionException;
import com.devops.ssh.exception.SshException;
import com.devops.ssh.exception.TimeoutException;
import com.devops.ssh.pool.SshClientPoolConfig;
import net.schmizz.sshj.DefaultConfig;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.connection.channel.direct.Session;
import net.schmizz.sshj.connection.channel.direct.Session.Command;
import net.schmizz.sshj.connection.channel.direct.Session.Shell;
import net.schmizz.sshj.transport.TransportException;
import net.schmizz.sshj.transport.verification.PromiscuousVerifier;
import net.schmizz.sshj.userauth.keyprovider.KeyProvider;
import net.sf.expectit.Expect;
import net.sf.expectit.ExpectBuilder;
import net.sf.expectit.ExpectIOException;
import net.sf.expectit.Result;
import net.sf.expectit.matcher.Matcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.SocketException;
import java.nio.channels.ClosedByInterruptException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static net.sf.expectit.filter.Filters.removeColors;
import static net.sf.expectit.filter.Filters.removeNonPrintable;
import static net.sf.expectit.matcher.Matchers.contains;
import static net.sf.expectit.matcher.Matchers.regexp;

/**
 *
 * build-in {@link SshClient} implementation  with <a href="https://github.com/hierynomus/sshj">hierynomus/SshJ</a>
 *
 * <p>Trouble and shooting:
 * <p>Problem: {@link #authPublickey()} throw exceptions contains "net.schmizz.sshj.common.Buffer$BufferException:Bad item length"
 * <p>Solution: may caused by key file format issue,use ssh-keygen on a remote Linux server to generate the key
 *
 *
 * @author Gary
 *
 */
public class SshClientSSHJ implements SshClient {

	private final static Logger logger = LoggerFactory.getLogger(SshClientSSHJ.class);

	private SshClientConfig clientConfig;

	private SSHClient client;

	private Expect expect = null;

	private Session session = null;

	private Shell shell = null;

	private boolean shellMode = false;

	private SshClientState state = SshClientState.inited;

	private SshClientEventListener eventListener;

	public String commandPromotRegexStr = "[\\[]?.+@.+~[\\]]?[#\\$] *";

	public Matcher<Result> commandPromotRegex = regexp(commandPromotRegexStr);

	// initialize DefaultConfig will consume resources, so we should cache it
	private static DefaultConfig defaultConfig = null;

	public static DefaultConfig getDefaultConfig() {
		if(defaultConfig==null) {
			defaultConfig = new DefaultConfig();
		}
		return defaultConfig;
	}

	/**
	 * used in shell mode, once it start session with server, the server will return promot to client
	 * <p>the promot looks like [centos@ip-172-31-31-82 ~]$
	 * <p>if the build-in one does not fit, you can change it by {@link SshClientPoolConfig#setServerCommandPromotRegex(String)}
	 * @param promot used to match promot from server
	 */
	public void setCommandPromotRegexStr(String promot) {
		this.commandPromotRegexStr = promot;
		this.commandPromotRegex = regexp(this.commandPromotRegexStr);
	}

	@Override
	public SshClient init(SshClientConfig config) {
		this.clientConfig = config;
		return this;
	}

	private void validate() throws SshException {
		if(this.clientConfig == null) {
			throw new SshException("missing client config");
		}
	}

	@Override
	public SshClient connect(int timeoutInSeconds) throws SshException{
		this.validate();
		if (timeoutInSeconds <= 0) {
			timeoutInSeconds = Integer.MAX_VALUE;
		} else {
			timeoutInSeconds = timeoutInSeconds * 1000;
		}
		return this.connect(timeoutInSeconds, false);
	}

	private SshClient connect(int timeoutInSeconds, boolean retry) throws SshException{
		logger.debug("connecting to " + this.clientConfig.getHost() + " port:" + this.clientConfig.getPort() + " timeout in:"
				+ (timeoutInSeconds / 1000) + " s");
		client = new SSHClient(getDefaultConfig());
		try {
			client.setConnectTimeout(timeoutInSeconds);
			client.addHostKeyVerifier(new PromiscuousVerifier());
			// client.loadKnownHosts();
			client.connect(this.clientConfig.getHost().trim(), this.clientConfig.getPort());
			logger.debug("connected to " + this.clientConfig.getHost().trim() + " port:" + this.clientConfig.getPort());
		} catch (TransportException e) {
			if(!retry) {
				logger.error("sshj get exception when connect and will retry one more time ", e);
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e1) {
				}
				return this.connect(timeoutInSeconds, true);
			}else {
				String errorMessage ="connect to " + this.clientConfig.getHost().trim() + " failed";
				logger.error(errorMessage, e);
				throw new SshException(errorMessage, e);
			}
		} catch (Exception e) {
			String errorMessage ="connect to " + this.clientConfig.getHost().trim() + " failed";
			logger.error(errorMessage, e);
			throw new SshException(errorMessage, e);
		}
		return this;
	}

	@Override
	public SshClient setEventListener(SshClientEventListener listener) {
		this.eventListener = listener;
		return this;
	}

	@Override
	public SshClient authPassword() throws SshException {
		try {
			logger.debug("auth with password");
			client.authPassword(this.clientConfig.getUsername(), this.clientConfig.getPassword());
		} catch (Exception e) {
			String errorMessage = "ssh auth " + this.clientConfig.getHost() + " fail";
			logger.error(errorMessage, e);
			throw new AuthException(errorMessage, e);
		}
		return this;
	}

	@Override
	public SshClient authPublickey() throws SshException {
		try {
			logger.debug("auth with key:"+this.clientConfig.getUsername()+","+this.clientConfig.getPrivateKeyPath());
			if (this.clientConfig.getPrivateKeyPath() != null) {
				KeyProvider keys = client.loadKeys(this.clientConfig.getPrivateKeyPath());
				client.authPublickey(this.clientConfig.getUsername(), keys);
			} else {
				client.authPublickey(this.clientConfig.getUsername());
			}
		} catch (Exception e) {
			String errorMessage = "ssh auth " + this.clientConfig.getHost() + " fail";
			logger.error(errorMessage, e);
			throw new AuthException(errorMessage, e);
		}
		return this;
	}

	@Override
	public SshClient startSession(boolean shellMode) {
		logger.info("start session " + (shellMode ? " in shellMode" : ""));
		try {
			session = client.startSession();
			this.shellMode = shellMode;
			if (shellMode) {
				session.allocateDefaultPTY();
				shell = session.startShell();
				shell.changeWindowDimensions(1024, 1024, 20, 20);
				this.renewExpect(60);
				expect.expect(commandPromotRegex);
			}
			this.state = SshClientState.connected;
			try {
				if(this.eventListener!=null) {
					this.eventListener.didConnected(this);
				}
			} catch (Exception e) {
			}
		} catch (Exception e) {
			if(e instanceof ExpectIOException) {
				ExpectIOException ioException = (ExpectIOException)e;
				logger.error("start session fail with server input:"+ioException.getInputBuffer().replaceAll("[\\\n\\\r]", ""), e);
			}else {
				logger.error("start session fail", e);
			}
			this.disconnect();
			throw new RuntimeException("start session fail." + e.getMessage());
		} finally {
			// close expect
			try {
				if (expect != null) {
					expect.close();
				}
			} catch (IOException e) {
				logger.error("close IO error", e);
			}
			expect = null;
		}
		return this;
	}

	@Override
	public SshResponse executeCommand(String command, int timeoutInSeconds) {
		if (this.shellMode) {
			return this.sendCommand(command, timeoutInSeconds);
		} else {
			return this.executeCommand_(command, timeoutInSeconds);
		}
	}

	private SshResponse executeCommand_(String command, int timeoutInSeconds) {
		logger.info("execute command: " + command);
		SshResponse response = new SshResponse();
		try {
			Command cmd = session.exec(command);
			if (timeoutInSeconds < 0) {
				cmd.join(Long.MAX_VALUE, TimeUnit.SECONDS);
			} else {
				cmd.join(timeoutInSeconds, TimeUnit.SECONDS);
			}
			BufferedReader reader = new BufferedReader(new InputStreamReader(cmd.getInputStream(), "UTF-8"));
			BufferedReader error_reader = new BufferedReader(new InputStreamReader(cmd.getErrorStream(), "UTF-8"));
			List<String> outputLines = new ArrayList<>();
			logger.debug("finish executing command on " + this.clientConfig.getHost() + ", console:");
			String outputLine;
			while ((outputLine = error_reader.readLine()) != null) {
				logger.debug(outputLine);
				outputLines.add(outputLine);
			}
			while ((outputLine = reader.readLine()) != null) {
				logger.debug(outputLine);
				outputLines.add(outputLine);
			}
			response.setStdout(outputLines);
			logger.info(
					"execute ssh command on " + this.clientConfig.getHost() + " completed, with exit status:" + cmd.getExitStatus());
			response.setCode(cmd.getExitStatus());
		} catch (Exception e) {
			if (e.getCause() instanceof InterruptedException || e.getCause() instanceof java.util.concurrent.TimeoutException) {
				logger.error("execute ssh on " + this.clientConfig.getHost() + " timeout");
				response.setException(new TimeoutException("execute ssh command timeout"));
			} else {
				logger.error("execute ssh on " + this.clientConfig.getHost() + ", command error", e);
				response.setException(new SshException("execute ssh command error "+e.getMessage()));
			}
		}finally {
			try {
				if(this.eventListener!=null) {
					this.eventListener.didExecuteCommand(this);
				}
			} catch (Exception e) {
			}
		}
		return response;
	}

	private SshResponse sendCommand(String command, int timeoutInSeconds) {
		SshResponse response = new SshResponse();
		if (this.state != SshClientState.connected) {
			response.setException(new LostConnectionException("client not connected"));
			response.setCode(0);
			return response;
		}
		try {
			this.renewExpect(timeoutInSeconds);
			// start expect
			logger.info(this + " execute command : " + command);
			expect.send(command);
			logger.debug(this + " command sent ");
			if (!command.endsWith("\n")) {
				expect.send("\n");
				logger.debug(this + " command \\n sent ");
			}
			Result result2 = expect.expect(contains(command));
			Result result = expect.expect(commandPromotRegex);
			logger.debug("command execute success with raw output");
			logger.debug("------------------------------------------");
			String[] inputArray = result.getInput().split("\\r\\n");
			List<String> stout = new ArrayList<String>();
			if(inputArray.length>0) {
				for(int i=0;i<inputArray.length;i++) {
					logger.debug(inputArray[i]);
					if(i==inputArray.length-1 && inputArray[i].matches(commandPromotRegexStr)) {
						break;
					}
					stout.add(inputArray[i]);
				}
			}
			logger.debug("------------------------------------------");
			response.setStdout(stout);
			response.setCode(0);
			logger.info("execute ssh command on " + this.clientConfig.getHost() + " completed, with code:" + 0);
		} catch (Exception e) {
			response.setCode(1);
			response.setException(new SshException(e.getMessage()));
			logger.error("execute command fail", e);
			if(e instanceof ArrayIndexOutOfBoundsException) {
				// server may be shutdown
				response.setException(new LostConnectionException("lost connection"));
				this.disconnect();
			} else if (e instanceof ClosedByInterruptException) {
				response.setException(new TimeoutException("execute command timeout"));
				this.sendCtrlCCommand();
			}
			else if (e.getCause() instanceof SocketException) {
				// the socket may be closed
				response.setException(new LostConnectionException("lost connection"));
				this.disconnect();
			} else if (e.getMessage().contains("timeout")) {
				response.setException(new TimeoutException("execute command timeout"));
				this.sendCtrlCCommand();
			}
			else {
				this.sendCtrlCCommand();
			}
		} finally {
			// close expect
			try {
				if (expect != null) {
					expect.close();
				}
			} catch (IOException e) {
				logger.error("close IO error", e);
			}
			expect = null;
			try {
				if(this.eventListener!=null) {
					this.eventListener.didExecuteCommand(this);
				}
			} catch (Exception e) {
			}
		}
		return response;
	}

	private void renewExpect(int timeoutInSeconds) throws IOException {
		if (expect!=null) {
			try {
				expect.close();
			}catch(Exception e) {
				e.printStackTrace();
			}
		}
		expect = new ExpectBuilder().withOutput(shell.getOutputStream())
				.withInputs(shell.getInputStream(), shell.getErrorStream())
				.withInputFilters(removeColors(), removeNonPrintable()).withExceptionOnFailure()
				.withTimeout(timeoutInSeconds, TimeUnit.SECONDS).build();
	}

	private void sendCtrlCCommand() {
		try {
			logger.debug("send ctr-c command ... ");
			expect.send("\03");
			expect.expect(commandPromotRegex);
			logger.debug("send ctr-c command success ");
		} catch (IOException e1) {
			logger.error("send ctrl+c command fail", e1);
		}
	}

	@Override
	public void disconnect() {
		if(this.state== SshClientState.disconnected) {
			return;
		}
		this.state = SshClientState.disconnected;
		try {
			if (shell != null) {
				shell.close();
			}
		} catch (IOException e) {
			logger.error("close ssh shell error", e);
		}
		try {
			if (session != null) {
				session.close();
			}
		} catch (IOException e) {
			logger.error("close sesion error", e);
		}
		try {
			if (client != null) {
				client.disconnect();
				client.close();
			}
		} catch (IOException e) {
			logger.error("close ssh conenction error", e);
		}
		logger.debug("ssh disconnect");
		try {
			if(this.eventListener!=null) {
				this.eventListener.didDisConnected(this);
			}
		} catch (Exception e) {
		}

	}

	@Override
	public SshClientState getState() {
		return this.state;
	}
}
package com.devops.ssh;

/**
 *
 * state of SshClient, See {@link SshClient#getState()} for more information
 *
 * @author Gary
 *
 */
public enum SshClientState {
	inited,
	connected,
	disconnected
}
package com.devops.ssh;

import java.util.ArrayList;
import java.util.List;

/**
 *
 * Response return from {@link SshClient#executeCommand(String, int)}
 *
 * @author Gary
 *
 */
public class SshResponse {

	private int code;

	private Exception exception;

	private List<String> stdout = new ArrayList<String>();

	/**
	 * @return 0
	 */
	public int getCode() {
		return code;
	}

	public void setCode(int code) {
		this.code = code;
	}

	/**
	 *
	 * @return the exception in {@link SshClient#executeCommand(String, int)}
	 */
	public Exception getException() {
		return exception;
	}

	public void setException(Exception exception) {
		this.exception = exception;
	}

	/**
	 *
	 * @return the output from remote server after send command
	 */
	public List<String> getStdout() {
		return stdout;
	}

	public void setStdout(List<String> stdout) {
		this.stdout = stdout;
	}



}

 运行测试Linux命令

echo 'yes'

 运行测试 shell 脚本

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/45366.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

云计算——ACA学习 数据中心概述

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​ 目录 写在前面 课程目标 学前了解 一.数据中心定义 二.数据中心涉及的主要标准与规范 …

和chatgpt学架构04-路由开发

目录 1 什么是路由2 如何设置路由2.1 安装依赖2.2 创建路由文件2.3 创建首页2.4 编写HomePage2.5 更新路由配置2.6 让路由生效 3 测试总结 要想使用vue实现页面的灵活跳转&#xff0c;其中路由配置是必不可少的&#xff0c;我们在做开发的时候&#xff0c;先需要了解知识点&…

十、数据结构——链式队列

数据结构中的链式队列 目录 一、链式队列的定义 二、链式队列的实现 三、链式队列的基本操作 ①初始化 ②判空 ③入队 ④出队 ⑤获取长度 ⑥打印 四、循环队列的应用 五、总结 六、全部代码 七、结果 在数据结构中&#xff0c;队列&#xff08;Queue&#xff09;是一种常见…

【数据分享】1999—2021年地级市地方一般公共预算收支状况(科学技术支出/教育支出等)

在之前的文章中&#xff0c;我们分享过基于2000-2022年《中国城市统计年鉴》整理的1999-2021年地级市的人口相关数据、各类用地面积数据、污染物排放和环境治理相关数据、房地产投资情况和商品房销售面积、社会消费品零售总额和年末金融机构存贷款余额&#xff08;可查看之前的…

STM32CubeIDE(串口)

目录 一、轮询模式 1.1 配置USART2为异步模式 1.2 500ms发送一次消息 1.3 通信结果 1.4 串口控制LED 二、中断收发 2.1 开启中断 2.2 中断发送接收 2.2.1 中断发送只需要调用接口 2.2.2 中断接收 2.3 实验结果 三、DMA模式与收发不定长数据 3.1 DMA通道配置 3.2 DMA…

【MATLAB绘图】

MATLAB绘图函数&#xff1a;Plot函数详解 介绍 MATLAB是一种常用的科学计算和数据可视化工具&#xff0c;它提供了强大的绘图函数&#xff0c;使用户能够创建各种类型的图表和图形。 基本语法 plot函数的基本语法如下&#xff1a; plot(x, y)其中&#xff0c;x和y是长度相…

Vue 本地应用 图片切换 v-show v-bind实践

点击切换图片的本质&#xff0c;其实修改的是img标签的src属性。 图片的地址有很多个&#xff0c;在js当中通过数组来保存多个数据&#xff0c;数组的取值结合索引&#xff0c;根据索引可以来判断是否是第一张还是最后一张。 图片的变化本质是src属性被修改了&#xff0c;属性…

Shell输出帮助手册

代码&#xff1a; 帮助手册雏形 function help(){echo -e "Help manual":echo -e " -h. -- help View the help manual"echo -e " install Installation"echo -e " uninstall Uninstall" }case "$1&qu…

设计模式——单例模式

1 概述 单例模式就是保证一个类只有一个对象实例。 为了保证无法创建多余的对象实例&#xff0c;单例类中需要自己创建对象实例&#xff0c;并把自己的构造方法私有化以防止其他地方调用创建对象&#xff0c;且需要提供一个公共的方法给其他类来获取该单例类的实例。 同时单例…

初识TDMQ

目录 一&#xff1a;需求背景二&#xff1a;相关文档三&#xff1a;验证TDMQ广播消息 一&#xff1a;需求背景 目前公司需要将决策引擎处理的结果&#xff0c; 一部分数据交给下游分析/入黑/通知等功能。因此就需要决策引擎生产结果让多方下游去消费。 而我需要实现下游的一部…

flutter开发实战-jsontodart及 生成Dart Model类

flutter开发实战-jsontodart及 生成Dart Model类。 在开发中&#xff0c;经常遇到请求的数据Json需要转换成model类。这里记录一下Jsontodart生成Dart Model类的方案。 一、JSON生成Dart Model类 在开发中经常用到将json转成map或者list。通过json.decode() 可以方便 JSON 字…

AMEYA360谈:村田新款超声波传感器,能实现15cm近距离检测

随着近年来ADAS的精度越来越高&#xff0c;对用于自动刹车和自动泊车的障碍物检测系统提出了更高的检测性能要求。配备在障碍物检测系统中的超声波传感器需要在短距离和长距离的情况下都具有很高的检测精度&#xff0c;并且谐振频率和静电容量的公差很小&#xff0c;以稳定精度…

AI学习笔记三:编写检测的yolov5测试代码

若该文为原创文章&#xff0c;转载请注明原文出处。 通过detect.py代码测试通过后&#xff0c;阅读detect.py代码发现&#xff0c;有些难以看懂&#xff0c;看得有点蒙蒙的&#xff0c; 所以编写了一个简单的测试程序。 代码如下&#xff1a; import cv2 import numpy as np…

基于AOP实现登录日志和操作日志(新手入门版)

基于AOP实现登录日志和操作日志 目录结构代码PostMan测试代码控制台查看输出解析成JSON如果你觉得对你有帮助的话&#xff0c;请点赞收藏 目录结构 代码 package com.demo.mymaintest.constants;import java.lang.annotation.Documented; import java.lang.annotation.ElementT…

Emvirus: 基于 embedding 的神经网络来预测 human-virus PPIs【Biosafety and Health,2023】

研究背景&#xff1a; Human-virus PPIs 预测对于理解病毒感染机制、病毒防控等十分重要&#xff1b;大部分基于 machine-learning 预测 human-virus PPIs 的方法利用手动方法处理序列特征&#xff0c;包括统计学特征、系统发育图谱、理化性质等&#xff1b;本文作者提出了一个…

全志F1C200S嵌入式驱动开发(spi-nor image制作)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 一般soc系统里面添加spi-nor flash芯片,特别是对linux soc来说,都是把它当成文件系统来使用的。spi-nor flash和spi-nand flash相比,虽然空间小了点,但是胜在稳定,这是很多工业…

linux编译内核

新安装的ubuntu18&#xff0c;补齐依赖工具包。 sudo apt install vim sudo apt install net-tools sudo apt-get install libncurses5-dev libssl-dev build-essential openssl sudo apt-get install flex sudo apt-get install bison -y sudo apt-get install openssh-s…

数据结构【栈和队列】

第三章 栈与队列 一、栈 1.定义&#xff1a;只允许一端进行插入和删除的线性表&#xff0c;结构与手枪的弹夹差不多&#xff0c;可以作为实现递归函数&#xff08;调用和返回都是后进先出&#xff09;调用的一种数据结构&#xff1b; 栈顶&#xff1a;允许插入删除的那端&…

logback-spring.xml日志配置文件详解

目录 前言logback-spring.xml 配置 前言 打印日志是一个系统的基本功能&#xff0c;系统出现异常可以通过查找日志弄清楚是什么原因&#xff0c;从而更加快速地定位问题&#xff0c;修复系统。 logback-spring.xml 配置 文件位置 具体配置 <?xml version"1.0"…

代理模式(java)

目录 结构 静态代理案例 代码实现 售票类 火车站类 代理类 测试类 优缺点 优点 缺点 结构 代理&#xff08;Proxy&#xff09;模式分为三种角色&#xff1a; 抽象主题&#xff08;Subject&#xff09;类&#xff1a; 通过接口或抽象类声明真实主题和代理对象实现的业务…