Spring batch project. There is a SshIService that accepts ssh commands and executes them. When the project runs in the Jenkins pipeline, it is able to create the ssh connections and my SshIService runs just fine.
While writing the Integration test for this SshIService service, I created a dummy SSH server which this service can talk to.
Problem: In the Integration test, when I am sending commands like mkdir src/test/resources/Folder1 ; ls src/test/resources/Folder1 ;
the error is:
mkdir: ls: File exists
mkdir: src/test/resources/Folder1: File exists
mkdir: ;: File exists
The service is not considering these are separate commands and rather as a parameter for mkdir
which is WRONG!
The problem is not specific to mkdir
. Every time, more than 1 command separated by a semi-colon is executed in the Integration test, the issue arises.
I don't want to make changes to SshIService coz the entire project had been working using that logic. The issues seem to be in the SSH server created for the Integration test.
Code for Integration test
@SpringBootTest
@ContextConfiguration(classes = {SshIService.class, SshSftpConfiguration.class})
@ActiveProfiles("test")
public class SshSftpIntegrationTest {
@Autowired
private SshIService SshIService;
private SshServer dummy_sshd;
@SneakyThrows
@BeforeEach
public void setUp() {
dummy_sshd = SshServer.setUpDefaultServer();
dummy_sshd.setPort(9999);
dummy_sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider());
dummy_sshd.setPasswordAuthenticator((username, password, session) -> username.equals("someUserName") && password.equals("somePassword"));
dummy_sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
dummy_sshd.setCommandFactory(new ProcessShellCommandFactory());
dummy_sshd.start();
}
@SneakyThrows
@AfterEach
public void cleanup() {
dummy_sshd.stop();
}
@SneakyThrows
@Test
public void sendCommandTest() {
SshIService.sendCommand("mkdir src/test/resources/Folder1 ; ls src/test/resources/Folder1 ;");
//Some assertions!
}
}
Code for SshIService
@Slf4j
@Service
public class SshIService {
private final SshSftpConfiguration sshSftpConfiguration;
private final JSch sshTunnel;
private Session sshSession;
private ChannelSftp channelSftp;
public SshIService(SshSftpConfiguration sshSftpConfiguration,
JSch sshTunnel) {
this.sshSftpConfiguration = sshSftpConfiguration;
this.sshTunnel = sshTunnel;
}
public String sendCommand(String command) throws IOException, JSchException, InterruptedException {
try {
connect();
Channel channel = sshSession.openChannel("exec");
((ChannelExec) channel).setCommand(command);
channel.setInputStream(null);
((ChannelExec) channel).setErrStream(System.err);
InputStream in = channel.getInputStream();
channel.connect();
StringBuilder output = new StringBuilder();
int bytesRead;
byte[] buffer = new byte[1024];
while (!channel.isClosed()) {
while ((bytesRead = in.read(buffer, 0, buffer.length)) != -1) {
output.append(new String(buffer, 0, bytesRead));
}
Thread.sleep(100);
}
channel.disconnect();
return cmdOut;
} catch (IOException | JSchException | InterruptedException ioX) {
log.error("Error: {}", ioX.getMessage());
throw ioX;
}
}
public String getHostAddress() throws UnknownHostException {
return InetAddress.getLocalHost().getHostAddress();
}
public void connect() {
try {
if (sshSession == null || !sshSession.isConnected()) {
sshSession = sshTunnel.getSession(sshSftpConfiguration.getUsername(), sshSftpConfiguration.getHost());
sshSession.setPort(sshSftpConfiguration.getPort());
sshSession.setPassword(sshSftpConfiguration.getPassword());
sshSession.setConfig("StrictHostKeyChecking", "no");
}
if (sshSession != null && !sshSession.isConnected()) {
sshSession.connect();
}
if (sshSession != null && channelSftp == null || !channelSftp.isConnected()) {
channelSftp = (ChannelSftp) sshSession.openChannel("sftp");
channelSftp.connect();
}
} catch (JSchException e) {
log.error(ExceptionUtils.getStackTrace(e));
}
}
public ChannelSftp getChannelSftp() {
return channelSftp;
}
public void setChannelSftp(ChannelSftp channelSftp) {
this.channelSftp = channelSftp;
}
public Session getSshSession() {
return sshSession;
}
public void setSshSession(Session sshSession) {
this.sshSession = sshSession;
}
}
Any suggestions?
ChannelSftp
why don't you use its native methods (mkDir()
,ls()
, etc) instead of using an exec channel?SshIService.getChannelSftp().mkDir()
after callingconnect()
instead of callingsendCommand()
to use the exec channel.sshSftpService.getChannelSftp().mkdir("src/test/resources/Folder3/test");
,sshSftpService.getChannelSftp()
is null.connect()
first - the field is set up there.