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

news2024/11/23 2:22:07

 基础知识

基于如下技术栈开发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 脚本

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Datawhale夏令营】任务一学习笔记

目录 一&#xff1a;anaconda的环境配置 二&#xff1a;赛题任务解读 2.1 任务要求 2.2 数据集介绍 2.3 评估指标 三&#xff1a;机器学习之 LightGBM 一&#xff1a;anaconda的环境配置 下载Anaconda&#xff1a;访问Anaconda官方网站&#xff08;https://www.anaconda.…

SQL篇-04_SQL进阶挑战-01_增删改操作

插入记录 SQL110 插入记录&#xff08;一&#xff09; 描述 牛客后台会记录每个用户的试卷作答记录到exam_record表&#xff0c;现在有两个用户的作答记录详情如下&#xff1a;用户1001在2021年9月1日晚上10点11分12秒开始作答试卷9001&#xff0c;并在50分钟后提交&#xff…

【算法基础:数学知识】4.3 欧拉函数

文章目录 欧拉函数定义性质 例题列表873. 欧拉函数&#xff08;使用质因数分解求一个数的欧拉函数&#xff09;原理讲解&#xff08;公式推导&#xff09;⭐解法代码 874. 筛法求欧拉函数&#xff08;求 1 ~ n 中所有数字的欧拉函数&#xff09;⭐ 欧拉函数 https://oi-wiki.o…

安装及配置zabbix_agent代理端(监控FTP服务器)

监控agent的linux主机我们在之前的文章里已经做好了 现在直接安装ftp服务即可 [rootagent ~]# yum install -y vsftpd[rootagent ~]# systemctl start vsftpd #启动ftp服务[rootagent ~]# systemctl enable vsftpd #设置ftp服务开机自启 Created symlink fro…

codec2play流程总结

Codec2.0(C2)是android系统为vendor提供的用于实现video/audio/filter模块的的HAL层接口API&#xff0c;vendor可用这个API实现他们自己的HAL层&#xff0c;Codec2.0是用于替换现有的OMX-IL。 数据流程 C2LinearBlock创建share ptr类型block&#xff0c;fetchLinearBlock对blo…

【每日一题Day274】LC42接雨水 | 单调栈

接雨水【LC42】[面试常见] 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 按列求贡献&#xff1a;枚举 首先确定按行计算雨水&#xff0c;还是按列确定雨水 按行计算&#xff1a; 按列计算&#xff1…

SpringBoot之jackson之复杂XML和Object互转、泛型、传参/接参

引入依赖 <!-- lombok插件 --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!-- jackson xml 转换工具 --><dependency><…

自然语言处理实战项目13-基于GRU模型与NER的关键词抽取模型训练全流程

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下自然语言处理实战项目13-基于GRU模型与NER的关键词抽取模型训练全流程。本文主要介绍关键词抽取样例数据、GRU模型模型构建与训练、命名实体识别(NER)、模型评估与应用&#xff0c;项目的目标是通过训练一个GRU模型…

crmeb部署

安装宝塔 教程 安装所需要的软件 php mysql5.7 redis fileinfo nginx 安装crmeb 重启mysql 前台http://192.168.216.128/ 后台http://192.168.216.128/admin admin admin888登录 访问前台

GOF 代理模式

1.需求 &#xff08;1&#xff09;&#xff1a;在程序中&#xff0c;对象A和对象B无法直接交互时。 &#xff08;2&#xff09;&#xff1a;在程序中&#xff0c;功能需要增强时。 &#xff08;3&#xff09;&#xff1a;在程序中&#xff0c;目标需要被保护时 代理模式中有一…

使用html和css技巧提升网站加载速度

使用html和css技巧提升网站加载速度 加载时间每增加一秒&#xff08;0-5 秒之间&#xff09;&#xff0c;网站转化率平均就会下降 4.42%。页面加载时间的前五秒对转化率的影响最大。 通过更改html和css文件可以提高网站的页面加载速度,本文现在就来介绍一下怎么实现。 延迟加载…

51单片机--AT24C02数据存储

文章目录 存储器的介绍AT24C02I2C总线I2C时序结构AT24C02数据帧AT24C02数据存储实例 存储器的介绍 存储器是计算机系统中的一种重要设备&#xff0c;用于存储程序和数据&#xff0c;它可以通过电子、磁性介质等技术来记录和保持数据。在这里&#xff0c;主要介绍的是随机存储器…

Java SPI机制:扩展Java应用的灵活性与可插拔性

文章目录 引言1. Java SPI机制简介2. Java SPI的工作原理2.1. 定义服务接口2.2. 编写服务提供者2.3. 创建SPI配置文件2.4. 使用Service Loader加载服务2.5. 客户端代码调用服务 3. 实例演示HelloEnService .javaHelloZhServiceImpl .javaMETA-INF/services/com.gpj.spi.HelloSe…

Blazor前后端框架Known-V1.2.6

V1.2.6 Known是基于C#和Blazor开发的前后端分离快速开发框架&#xff0c;开箱即用&#xff0c;跨平台&#xff0c;一处代码&#xff0c;多处运行。 Gitee&#xff1a; https://gitee.com/known/KnownGithub&#xff1a;https://github.com/known/Known 概述 基于C#和Blazor…

kubesphere安装中间件

kubesphere安装mysql 创建configMap [client] default-character-setutf8mb4[mysql] default-character-setutf8mb4[mysqld] init_connectSET collation_connection utf8mb4_unicode_ci init_connectSET NAMES utf8mb4 character-set-serverutf8mb4 collation-serverutf8mb4_…

Nuxt 菜鸟入门学习笔记一:介绍与安装

文章目录 介绍 Introduction自动化和惯例服务器端渲染服务器引擎生产就绪模块化架构 安装 Installation准备安装 Nuxt官网地址&#xff1a; https://nuxt.com/ 介绍 Introduction Nuxt 是一个免费的开源框架&#xff0c;以直观和可扩展的方式使用 Vue.js 创建类型安全、高性能…

SQL篇-04_SQL进阶挑战-02_ 表与索引操作

SQL118 创建一张新表 描述 现有一张用户信息表&#xff0c;其中包含多年来在平台注册过的用户信息&#xff0c;随着牛客平台的不断壮大&#xff0c;用户量飞速增长&#xff0c;为了高效地为高活跃用户提供服务&#xff0c;现需要将部分用户拆分出一张新表。 原来的用户信息表&…

【指针和数组笔试题(1)】详解指针、数组笔试题

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言整型数组字符数组第一组题第二组题第三组题 总结 前言 在计算之前要了解基本概念&#xff1a; 数组名的理解 数组名是数组首元素的地址 有两个例外 1.sizeof(…

小白到运维工程师自学之路 第五十八集 (zabbix监控数据库)

一、为server.Zabbix.com添加服务模板 二、配置数据库 cd /usr/local/zabbix/etc/ vim zabbix_agentd.conf 添加配置项 UnsafeUserParameters1 //允许所有字符的参数传递给用户定义的参数。 UserParametermysql.version,mysql -V //定义键值mysql.version&a…