Liferay DevOps

March 22, 2017

DevOps became necessary tool/approach in developer and sysadmin life. Most of companies introduced or will introduce own mechanisms to automate internal software processes. There are many programs that simplify and automate software development, delivery and integration. Combination of DevOps tools (for example Docker, Git, Jenkins, Bamboo, Bash scripts, Nagios etc.) can minimize human effort.

Running technical operations is one part. There is also business part that can be automated. After new application or release is delivered, there are tasks that need to be done on running web application. For example specific content need to be published, user password need to be reset, email need to be sent etc. This requirement appears very often during my work with Liferay. How to automate post delivery processes? Liferay allow you to run scripts via browser (Groovy, Javascript, Python, Beanshell, Ruby scripts in control panel)

Unfortunately this approach is not very automation friendly. This is why i have built simple portlet (currently for Liferay 6.2 but version for DXP is very similar) to automate groovy script execution.

Our portlet (called lfrdevops) allow to execute groovy scripts from file stored in a given location. Script can be invoked many ways

via curl (portlet need to be placed on public page if we want to skip authentication):

curl "http://localhost:8080/web/guest/home?p\_p\_id=lfrdevops\_WAR\_lfrdevops&p\_p\_lifecycle=1&p\_p\_state=normal&p\_p\_mode=view&p\_p\_col\_id=column-1&p\_p\_col\_pos=2&p\_p\_col_count=3" -H "Host: localhost:8080"

via browser interface:

We can place our portlet on the page authomatically using many options, for example in init method or via LAR

There are 2 important properties to set in portal-ext.properties:

# path to file with groovy script (file should have read write permissions)

liferay.devops.groovy.file.path={liferay.home}/groovy_script.txt

# we need to add portlet lfrdevops_WAR_lfrdevops name if we want skip auth token (therfore portlet action url will not change)

auth.token.ignore.portlets=lfrdevops_WAR_lfrdevops

Url to invoke script does not change because portlet has no instance. Url will change only when portlet is moved to another page. We can ensure that portlet is on the page via LAR import/export or custom code on init portlet.

For security reason, we rename groovy file after each execution. This will ensure that script was invoked only once. We simply append execution time to file name.

File name before execution:

groovy_script.txt

File name after execution:

groovy\_script.txt\_executed\_2017\_03\_04\_02\_20\_439

This is very simple version of the portlet to demonstrate how to automate groovy scripts. We can secure execution many ways: authentication, proxy etc.

package com.dev.lfrdevops;
 
 import com.liferay.portal.kernel.exception.PortalException;
 import com.liferay.portal.kernel.io.unsync.UnsyncByteArrayOutputStream;
 import com.liferay.portal.kernel.io.unsync.UnsyncPrintWriter;
 import com.liferay.portal.kernel.log.Log;
 import com.liferay.portal.kernel.log.LogFactoryUtil;
 import com.liferay.portal.kernel.scripting.ScriptingException;
 import com.liferay.portal.kernel.scripting.ScriptingHelperUtil;
 import com.liferay.portal.kernel.scripting.ScriptingUtil;
 import com.liferay.portal.kernel.util.PropsUtil;
 import com.liferay.portal.kernel.util.StringPool;
 import com.liferay.portal.kernel.util.UnsyncPrintWriterPool;
 import com.liferay.util.bridges.mvc.MVCPortlet;
 
 import javax.portlet.ActionRequest;
 import javax.portlet.ActionResponse;
 import javax.portlet.PortletException;
 import java.io.BufferedReader;
 import java.io.FileNotFoundException;
 import java.io.FileReader;
 import java.io.IOException;
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.Map;
 
 public class LfrDevOpsPortlet extends MVCPortlet {
 
     private static Log _log = LogFactoryUtil.getLog(LfrDevOpsPortlet .class);
 
     private static final String FILENAME = PropsUtil.get(LfrDevOpsConstants.GROOVY_FILE_PATH);
     private static final String datePattern = "yyyy_MM_dd_hh_mm_SSS";
     public void processAction(ActionRequest actionRequest, ActionResponse actionResponse)
             throws IOException, PortletException {
         _log.info("Groovy File path in portal-ext.properties: "+LfrDevOpsConstants.GROOVY_FILE_PATH+"="+FILENAME);
         java.io.File fileOld = new java.io.File(FILENAME);
         if(!fileOld.exists()){
             _log.error("File does not exist: "+FILENAME);
             return;
         }
         try {
                 String language = LfrDevOpsConstants.GROOVY;
                 String script = loadScriptFromFile();
                 Map<String, Object> portletObjects = ScriptingHelperUtil.getPortletObjects(getPortletConfig(), getPortletContext(),
                         actionRequest, actionResponse);
                 UnsyncByteArrayOutputStream unsyncByteArrayOutputStream = new UnsyncByteArrayOutputStream();
                 UnsyncPrintWriter unsyncPrintWriter = UnsyncPrintWriterPool.borrow(unsyncByteArrayOutputStream);
                 portletObjects.put("out", unsyncPrintWriter);
                 _log.info("Executing groovy script");
                 ScriptingUtil.exec(null, portletObjects, language, script, StringPool.EMPTY_ARRAY);
                 unsyncPrintWriter.flush();
                 String output = unsyncByteArrayOutputStream.toString();
                 actionResponse.setRenderParameter(LfrDevOpsConstants.SCRIPT_OUTPUT, output);
             } catch (ScriptingException e) {
                 _log.error(e.getMessage());
             } catch (PortalException e) {
                 _log.error(e);
             }
         String newFileName = FILENAME+"_executed_"+getExecutionTime();
         java.io.File fileNew = new java.io.File(newFileName);
         boolean success = fileOld.renameTo(fileNew);
         if(success){
             _log.info("Renamed file to: "+newFileName);
         }
         _log.info("LfrDevOps finished");
     }
 
     private String getExecutionTime() {
         Calendar cal = Calendar.getInstance();
         Date currentTime = cal.getTime();
         SimpleDateFormat format = new SimpleDateFormat(datePattern);
         String executionTime = format.format(currentTime);
         return executionTime;
     }
 
     private String loadScriptFromFile() {
         String script = StringPool.BLANK;
         _log.info("file: "+FILENAME);
         try {
             BufferedReader br = new BufferedReader(new FileReader(FILENAME));
             try {
                 StringBuilder sb = new StringBuilder();
                 String line = br.readLine();
                 while (line != null) {
                     script += line+System.getProperty("line.separator");
                     sb.append(line);
                     line = br.readLine();
                 }
                 String everything = sb.toString();
             } catch (IOException ioe) {
                 _log.error(ioe.getMessage());
             } finally {
                 br.close();
             }
         } catch (FileNotFoundException fnfe) {
             _log.error(fnfe.getMessage());
 
         } catch (IOException ioe) {
             _log.error(ioe.getMessage());
         }
         _log.info("reading file finished");
         return script;
     }
 
 }


Code above comply with java 6, 7 und 8. Of course it can be optimized for specific java.

Sample groovy script that reindexes web content portlet:

import com.liferay.portal.service.PortletLocalServiceUtil;
import com.liferay.portal.util.*;
import com.liferay.portal.kernel.search.*;
import com.liferay.portal.kernel.dao.shard.*;

companyId = PortalUtil.getDefaultCompanyId();
companyIdString = ""+companyId;
String[] companyIds = [companyIdString]
portletId="15"
portlet=PortletLocalServiceUtil.getPortletById(companyId, portletId)
indexes = portlet.getIndexerInstances();
for(index in indexes){
         searchEngineId = index.getSearchEngineId()
         SearchEngineUtil.deletePortletDocuments(searchEngineId, companyId, portletId, true);
}
for(index in indexes){
ShardUtil.pushCompanyService(companyId);
         try {
                   index.reindex(companyIds);
         } catch (Exception e) {
                   println(e.getMessage())
         } finally {
                   ShardUtil.popCompanyService();
         }
}
println("Indexing finished for portlet: "+portletId)

You can download whole portlet lfrdevops.

About the author: Piotr Swiniarski
Comments
Join us