@pperreault good stuff.
If I were to implement this in product I would probably write a regex with capture groups to match a valid user line. That way you can be more sure than the 9 column line is actually a user entry, and you can use the capture groups to easily dump out other items of interest.
This is a pretty common pattern in our core DataSources, most of the Nimble Storage stuff follows this pattern.
The regexes in those scripts might look scary but it's pretty easy to make in something like https://regexr.com
It's pretty solid looking for a first Groovy script. I would consider exiting the SSH session before you process the output, no need to keep that connection open after you have the data in a variable.
As far as error handling, you could wrap the initial Expect session setup in a try/catch. You can also use exitValue() on your Expect client object to ensure that your `show` command returned a successful return code before parsing the output.
Here's a pretty extreme example of a Groovy script with multiple return codes for specific issues:
import rocks.xmpp.addr.Jid
import rocks.xmpp.core.XmppException
import rocks.xmpp.core.sasl.AuthenticationException
import rocks.xmpp.core.session.TcpConnectionConfiguration
import rocks.xmpp.core.session.XmppClient
import rocks.xmpp.core.session.XmppSessionConfiguration
import rocks.xmpp.core.session.debug.ConsoleDebugger
import rocks.xmpp.core.stanza.model.Message
import javax.net.ssl.*
// Server details, required
def host = hostProps.get("system.hostname")
def domain = hostProps.get("xmpp.domain")
def port = hostProps.get("xmpp.port") ?: '5222'
// Account details, required
def sender = hostProps.get("xmpp.sender")
def sender_pass = hostProps.get("xmpp.sender.pass")
def receiver = hostProps.get("xmpp.receiver")
def receiver_pass = hostProps.get("xmpp.receiver.pass")
// Optional, disable starttls by setting to 'false'
def use_ssl = hostProps.get("xmpp.ssl") ?: true
// Optional, set to "true" to enable debug output in wrapper.log
def debug = hostProps.get("xmpp.debug") ?: 'false'
// Optional, change default authentication mechanism
def auth_mechanism = hostProps.get("xmpp.authmech") ?: "PLAIN"
// time to wait for expected message ( in seconds )
def timeout = hostProps.get("xmpp.message.timeout") ?: 5
// Check for required props
if (!(sender && sender_pass && receiver && receiver_pass && domain))
{
println 'missing required properties'
println "xmpp.sender, xmpp.sender_pass, xmpp.receiver, xmpp.receiver_pass, and xmpp.domain are required"
return 1;
}
// Used as a lock for synchronizing
def received = new Object();
// Setup a bogus trust manager to accept any cert
def nullTrustManager = [
checkClientTrusted: { chain, authType -> },
checkServerTrusted: { chain, authType -> },
getAcceptedIssuers: { null }
]
// Setup a bogus hostname verifier
def nullHostnameVerifier = [
verify: { hostname, session -> true }
]
// Setup an SSL Context with the bogus TM and HV to accept any cert
SSLContext sc = SSLContext.getInstance("SSL")
sc.init(null, [nullTrustManager as X509TrustManager] as TrustManager[], null)
public class NullHostnameVerifier implements HostnameVerifier
{
public boolean verify(String hostname, SSLSession session)
{
return true;
}
}
HostnameVerifier verifier = new NullHostnameVerifier();
// Store send and receive timestamps so we can calculate RTT ( in millis )
def send_time = null
def receive_time = null
// Check debug option, setup session accordingly
if (debug == 'true')
{
sessionConfiguration = XmppSessionConfiguration.builder()
.debugger(ConsoleDebugger.class)
.authenticationMechanisms(auth_mechanism)
.build();
}
else
{
sessionConfiguration = XmppSessionConfiguration.builder()
.authenticationMechanisms(auth_mechanism)
.build();
}
// Setup connection config
def tcpConfiguration = TcpConnectionConfiguration.builder()
.hostname(host) // The hostname.
.port(port.toInteger()) // The XMPP default port.
.sslContext(sc) // Use an SSL context, which trusts every server. Only use it for testing!
.hostnameVerifier(verifier)
.secure(use_ssl) // We want to negotiate a TLS connection.
.build();
// create the client instance
def xmppClientSender = XmppClient.create(domain, sessionConfiguration, tcpConfiguration);
def xmppClientReceiver = XmppClient.create(domain, sessionConfiguration, tcpConfiguration);
// Add a message listener to the client instance
xmppClientReceiver.addInboundMessageListener(
{ e ->
// Get the message
Message message = e.getMessage();
// Confirm that this message is from the expected sender
if (message.from.toString() == "${sender}@${domain}/LMSENDER")
{
// Confirm that this message has the expected content
if (message.body == "TESTMESSAGE")
{
// Record receive time
receive_time = System.currentTimeMillis();
// Notify the parent thread that we can exit
// synchronized must be used for notify() to work
// received is our lock
synchronized (received)
{
// notify the main thread to resume now that we have received the expected message.
received.notify();
}
}
}
});
// Try to connect the receiver client
try
{
xmppClientReceiver.connect();
}
catch (XmppException e)
{
println 'Receiver client failed to connect';
return 2;
}
// Try to connect the sender client
try
{
xmppClientSender.connect();
}
catch (XmppException e)
{
println 'Sender client failed to connect';
return 3;
}
// Try to login and send a message
try
{
// We need to use synchronized here to call wait() on our lock object (received)
// This will wait for a message event from the expected user with the expected content
// The message handler will call notify() to resume this thread.
// If we don't do this the client will close before we get the message.
synchronized (received)
{
// Login with receiver account
xmppClientReceiver.login(receiver, receiver_pass, 'LMRECEIVER')
// Login with Sender account
xmppClientSender.login(sender, sender_pass, 'LMSENDER')
// Record time just before sending, so we can get RTT
send_time = System.currentTimeMillis()
// Send the message
xmppClientSender.send(new Message(Jid.of("${receiver}@example.com"), Message.Type.CHAT, "TESTMESSAGE"))
// Call wait() to block this thread until the message listener has called notify()
// or until the timeout.
received.wait(timeout * 1000)
}
if (send_time == null)
{
println "Message wasn't sent."
return 4;
}
if (receive_time == null)
{
println "Message wasn't received."
return 5;
}
// Print delivery time
println receive_time - send_time
}
catch (AuthenticationException e)
{
println 'Authentication issue. Check your credentials.'
println e;
return 6;
}
catch (XmppException e)
{
println 'Something else went wrong with XMPP.'
println e;
return 7;
}
finally
{
// Close the connections
xmppClientReceiver.close()
xmppClientSender.close()
}
return 0;