The udclient out-of-the-box is a one-shot tool. It runs a single instruction and then exits. There is a significant delay for each start-up of the udclient tool as it starts a JVM. So wouldn't it be nice to be able to use udclient interactively ?
In this article I'm presenting a very simple wrapper script that can be used to invoke multiple udclient commands in a single session. This can either be from a set of commands stored in a file or by interactively typing commands. I'm calling this the udShell.
A simple test of 4 udclient commands run individually vs in a single session showed 12 seconds for standalone operation and 6 seconds to run the same commands as one session.
The solution presented here is a simple wrapper that calls the udclient in the same process and thus avoids the overhead of multiple JVM starts. Since its just a wrapper it should in theory work with any version of the udclient.
There are some simple extras like being about to use the shell notation for variables ${var} and having these replaced with the value of an environment variable, but it would be a simple matter to take the value map from another source for example.
The wrapper is run from a shell taking the same basic option parameters like--weburl, --username and so on from the command line. The wrapper doesn't process any of these itself, it just passes them on to the udclient. Each udclient command taken from the input source is prepended with the scripts argument list so you no longer have to specify these on every command. You just enter a command like:
createVersion --component newcomp --name 1.0
If you wanted to do the same thing from a file, you might like to parameterise the input like:
createVersion --component newcomp --name ${ver}
All the options and environment variables processed by the udclient should work in the same way. If you don't provide the credentials on the command line or through the environment, you will be prompted for them on every command you type in the session.
This is an example of invoking the udShell interactively
[root@localhost udclient]# ./udShell.sh --weburl https://localhost:8443 --username admin --password admin
udShell - udclient Interactive Session
Type ctrl-d to exit
$> createVersion --component newComp --name 34.5
Executing: createVersion --component newComp --name 34.5
{
"id": "cae7eb67-992f-40fc-82ba-ec030889eb61",
"name": "34.5",
"type": "FULL",
"created": 1604943750145,
"active": true,
….
$> <Ctrl-d>
udShell complete
Here is an example script to execute using shell variable replacement to determine the component version to create.
createVersion --component newComp --name ${ver}
addVersionFiles --component newComp --version ${ver} --base baseDir
addVersionLink --component newComp --version ${ver} --linkName "link to build" --link http://builder.com
addVersionStatus --component newComp --version ${ver} --status Gold
and a run of the udShell executing it
[root@localhost udclient]# export ver=7.8
[root@localhost udclient]# cat cmds | ./udShell.sh --weburl https://localhost:8443 --username admin --password admin
Executing: createVersion --component newComp --name 7.8
{
"id": "7da64040-2e82-4be8-bf83-928227f77950",
"name": "7.8",
"type": "FULL",
"created": 1604943980927,
"active": true,
…
…
"properties": []
}],
"totalSize": 0,
"totalCount": 0
}
Exit Code = 0
Executing: addVersionFiles --component newComp --version 7.8 --base baseDir
Exit Code = 0
Executing: addVersionLink --component newComp --version 7.8 --linkName "link to build" --link http://builder.com
Operation succeeded.
Exit Code = 0
Executing: addVersionStatus --component newComp --version 7.8 --status Gold
Operation succeeded.
Exit Code = 0
udShell Complete
The main difference between running the udShell interactively rather than from a file is that in interactive mode, the udShell won't exit on an error whereas it will if the commands are coming from a file.
This wrapper should make the job of automating many admin tasks easier and also a lot quicker to execute. There is potential for the creation of a plugin step to use this to eliminate the step overhead and JVM overhead of running multiple udclient commands in a UCD process.
The Code
Now for the important bit. You can just copy the code using the button at the end of the listing and paste it into a new file. Following that is a bash script to run the udShell.
You will need to setup the GROOVY_HOME environment variable pointing at your groovy install. You will also need a copy of the udclient.jar file in your current directory.
import com.urbancode.ds.client.DeployCLI
import java.util.regex.Matcher
import java.util.regex.Pattern
import java.security.Permission
import org.apache.commons.lang.text.StrSubstitutor
/*
udClient Interative Shell
=========================
This wrapper script enables you to create an interactive session with the udclient so that you don't have to
instantiate a new JVM for each command. This can make a sequence of udclient commands a lot faster and also
saves you having to resort to the REST API to get the speed.
The wrapper can be run with either the input taken from a file or from the users console.
This is a pretty simple 'shell' and takes as parameters any of the standard udclient options that preceed an actual
command function list createVersion. Internally all of your command line options are passed to the udclient prefixing
the input that either comes from a file or that is typed in interactively.
The input can contain references to shell variables eg
createVesion --component newComp --name ${ver}
In which case the ${ver} would be substitued for the value of the ver environment variable.
You can terminate the session with ctrl-d.
*/
// Setup a java security manager to stop the udclient being able to terminate the JVM
SystemExitControl exitControl = new SystemExitControl();
exitControl.blockSystemExit();
// Decide if we're actually taking typed in input or if its coming from a file
def isInteractive = System.console() != null
if (isInteractive)
{
println "udShell - udclient Interactive Session"
println "Type ctrl-d to exit"
print '$> '
}
// Read the input until it is exhasted
System.in.eachLine {String line ->
try
{
// Substitute environment variables
line = StrSubstitutor.replace(line,System.getenv())
// Parse the line into individual arguments pre-pending the wrappers own argument list
String[] ags = this.args + parseArgs(line)
// Call the udclient to execute
println "Executing: ${line}"
DeployCLI.main(ags)
}
catch (Throwable e )
{
// here we're catching the udclient's attempt to exit and just reporting its return code before
// we process the next command from the input stream
println "Exit Code = ${e.toString()}"
// if we're not running in interactive mode then quit it a command fails
if ( ! isInteractive && e.toString() != "0" )
{
println "Terminating Script due to error"
exitControl.allowSystemExit()
System.exit (e.toString().toInteger())
}
}
// Output a prompt for the next line if we're actually interactive
if (isInteractive) print '$> '
}
// The input is exhasted, so re-enable the system.exit call so the JVM can terminate
println "udShell Complete"
exitControl.allowSystemExit();
String[] parseArgs(String cmdLine)
{
// just split this into space separated phrases, but honour quotes
List<String> list = new ArrayList<String>();
Matcher m = Pattern.compile("([^\"]\\S*|\".+?\")\\s*").matcher(cmdLine);
while (m.find())
list.add(m.group(1).replaceAll('"',""))
return list
}
// Security manager to catch and handle system.exit requests
public class BlockExitSecurityManager extends SecurityManager
{
private SecurityManager oldMgr = System.getSecurityManager();
public static class ExitCapturedException extends SecurityException
{
int exitCode;
ExitCapturedException (int code) { exitCode=code;}
public String toString()
{
return ("${exitCode}") ;
}
}
public void checkExit(int status)
{
super.checkExit(status);
// We've caught an exit call, raise this exception to allow it to be handled higher up
throw new ExitCapturedException(status);
}
public void checkPermission(Permission perm)
{
}
public SecurityManager getPreviousMgr()
{
return oldMgr;
}
}
public class SystemExitControl
{
public SystemExitControl()
{
super();
}
public void blockSystemExit()
{
// This method sets up a security manager to capture a System.exitr call we use
// this to capture exits form within the udclient code
SecurityManager securityManager = new BlockExitSecurityManager();
System.setSecurityManager(securityManager) ;
}
public void allowSystemExit()
{
// This method replaces the security manager we setup with a null one to
// re-enable system.exit so our own wrapper code can exit
SecurityManager origSecMgr = System.getSecurityManager();
if ((origSecMgr != null) && (origSecMgr instanceof BlockExitSecurityManager))
{
BlockExitSecurityManager smgr = (BlockExitSecurityManager) origSecMgr;
System.setSecurityManager(smgr.getPreviousMgr());
}
else
{
System.setSecurityManager(null);
}
}
}
Copy code
Source for the udShell.sh command
#!/bin/sh
if [ -n "$GROOVY_HOME" ]; then
# change the dir to the root of the client directory
SHELL_NAME="$0"
SHELL_PATH=`dirname "${SHELL_NAME}"`
if [ "." = "$SHELL_PATH" ]
then
SHELL_PATH=`pwd`
fi
groovycmd="$GROOVY_HOME/bin/groovy"
jarfile="$SHELL_PATH/udclient.jar"
if [ -r "$jarfile" ]; then
"$groovycmd" -cp "$jarfile" udShell.groovy "$@"
else
echo "Didn't find $jarfile in directory ${SHELL_PATH}"
exit 1
fi
else
echo You must have GROOVY_HOME set in your environment to use the udshell.
exit 1
fi
Copy code
I hope you will find this article and the udShell a useful addition to your UCD tools environment.
Would you like to see the udShell expanded? Please leave your comments below
by Alan Murphy
Cloud, DevOps, IT Specialist, Cloud Integration Expert Labs
IBM Cloud and Cognitive Software
Alan Murphy/UK/IBM
alan.murphy@uk.ibm.com
Alan Murphy is a services consultant who has worked with clients to help them adopt new tools and processes for the last 20 years. UrbanCode Deploy and DevOps has been his focus for the last 5+ years. He also develops tools to assist clients in tool adoption and blogs on an occasional basis.
#UrbanCode#DevOps#UrbanCodeDeploy#10minutetip