Gogo Shell via Rest API

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

WARNING so never , i repeat never ever execute the following commands. They stop the module framework.
  • 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

developer studio (question: is someone using it?) , try intelli J ;)

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>

If your instance is running fine you should get the same response that you get from the normal Admin Gogo shell interface i.e
 
No unregistered components found

and for commands which are not allowed in the API you will get the error response as

Status: 400 Bad Request
Error: Command not allowed

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 ......