Blogs
exposing gogo shell via rest api

Rest API for gogo commands
you read it right :) ,
the idea might sound weird to many but if you are like me and have been working on Liferay for quiet some time especially for clients with lots of custom OSGI modules , where one or more modules rely on each other ... ( which might be against the core idea of OSGI , but it is what it is ) or you have an instance / portal with highly coupled modules due to xyz reasons, (no one is judging i hope)
then you might have been heading over to Gogo Shell Interface available in the portal to do commands such as "dm wtf" a savior command if i am not wrong about it or other commands such as lb , b [BUNDLE_ID] , diag
If you are on the local / UAT environment u can use the
telnet localhost 11311
However on pre-prod / production such access is highly discouraged by Liferay and the only recommended way to access Gogo Shell is via Control Panel with proper administrative permissions which is correct as a single command executed by it can mess up your entire instance , stop the module framework entirely
-
close
-
exit
-
shutdown
Ok , now we all know and agree how critical the module framework and gogo shell admin panel are.
However In real world scenarios, when your client has X number of instances running in a cluster with X number of custom modules , and unexpected server restarts can lead to
-
unregistered components
-
bundles in resolved state
-
bundles in installed state
so in most cases the
Post-Restart Activity includes Login -> Navigation to Gogo Shell
-> running command " dm wtf " to verify that all
modules are OK
which in case of a single Server Instance is acceptable but if the cluster servers are like 8-10 , it just increases the deployment time
especially if the deployments are taken care by Pipelines (CI / CDs) and again we cannot use telnet to access gogo shell as it is not recommended
This is where the Idea / solution of exposing the few gogo commands come into action.
Why not have a custom rest module implementing a Rest API which executes the command of gogo shell and just returns you back the result?
sounds good right? and obviously Authorized too :D ..
Let's start shall we?
I use IntelliJ so the below steps of creating are for it, however just create a rest module in your workspace in any way, blade , eclipse
Jokes aside
Name your module anything i named it as gogoshellApi
Once your module is ready, Head over to java class
i have changed the endpoints to more clear,
JaxrsWhiteboardConstants.JAX_RS_APPLICATION_BASE + "=/operations",
JaxrsWhiteboardConstants.JAX_RS_NAME + "=Operations.Rest"
then head over to modules build.gradle and add another dependency
compileOnly group: 'org.apache.felix', name: 'org.apache.felix.gogo.command', version: '1.0.2'
This will let to do the magic :D
Now Head Back to your java class and let's start adding the new endpoint code..
First Thing, Taking in account of any disaster level command, let's maitain a list of commands that are allowed via this API create a list and store the allowed commands in our case , "dm wtf" , diag , "b"
private static final List<String> allowedGogoShellInputs = Arrays.asList("dm wtf", "diag", "b");
Add the two things in your class
@Reference
CommandProcessor commandProcessor;
Log log = LogFactoryUtil.getLog(GogoshellApiApplication.class);
Now Just add the code, and let's talk about it
@GET
@Path("/gogo/{command}")
public Response health(@NotNull @PathParam("command") String command) {
try {
String lowerInput = command.toLowerCase();
if (allowedGogoShellInputs.contains(lowerInput) || lowerInput.matches("b\\s+\\d+")) {
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
PrintStream printOut = new PrintStream(outStream);
CommandSession session = commandProcessor.createSession(
new ByteArrayInputStream(new byte[0]),
printOut,
System.err
);
session.execute(command);
String output = outStream.toString();
session.close();
return Response.ok(output).build();
} else {
return Response.status(Response.Status.BAD_REQUEST)
.entity("Error: Command not allowed")
.build();
}
} catch (Exception e) {
log.error(e);
Throwable cause = e.getCause() != null ? e.getCause() : e;
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Error: " + e.getMessage() + " Cause: " + cause)
.build();
}
}
It defines an endpoint "/gogo/{command}" and the command as the path param.
Firstly we are just checking if the command in the param is one of the allowed commands or not,
secondly as you know b commands take a [BUNDLE_ID] with it so just an additional regex to check it.
We are utilizing the "commandProcessor"
that we referred and it lets us create a
CommandSession which we use to execute the command
using session.execute(command);
while session creation it needs three parameters
- Input Stream (used for any input)
- we don't need anything so empty?
- Output
Stream (mostly used for the actual outputs)
- intially i tried using System.in (as suggested by our CHATGPT friend) , but it was always returning StackOverflow , so i just shifted to an independent stream and it works
- Output Stream (used for any internal error)
- default System.err
If you have done all the above steps , you are good to go. Build/Deploy the module on your running server preferably localhost first.. wait for it to start.
Head over to any API Platform , such as Postman? or anything you like.
assuming your server is running on localhost port 8080. this should be your endpoint
http://[host]:[port]/o/[application_base][API_PATH]
http://localhost:8080/o/operations/gogo/dm
wtf
use Auth Type as Basic
<Base64 encoded username and password>
and for commands which are not allowed in the API you will get the error response as
so there it is folks, your Gogo shell commands being executed via a Rest API.
I hope someone out there might find this useful :D
Code available at
https://github.com/jawaddpskw/gogoshellrestapi.git
Continuing my journey of exploring Liferay,
Signing out until next time ......