Developing An Ansible Role for Nexus Repository Manager v3.x

                                              


This article aims to explain how to automate the installation as well as configuration of Nexus’s Repository Manager version 3.x with Ansible
.

Ansible is a deployment tool, which enables playbooks to automate applications and infrastructure deployments. The key advantage is its flexibility to change applications with versatility as well as apprehending them as a service. However, Ansible has some weaknesses too. Following a voluntary simple design, it functions solely as an application of parameters without taking into account information availability and security concerns, which need to be handled by other systems. That is why developers will prefer to use Ansible in combination with a stateful agent system like Puppet, or a centralized management tool like Ansible Tower.

Automation with Ansible

Ansible focuses on the application-level setup, scripting a provisioning that can be run on top of any infrastructure-supporting tool (PaaS, containers, bare-metal, vagrant, etc.). It only needs an SSH connection and a sudo account to the remote system.

Provisioning scripts in Ansible are written in a declarative style using YAML files grouped as roles. The atomic instructions in those roles are expressed using a number of core modules provided by Ansible. Please have a look at the Ansible documentation for an in-depth introduction.

Re-provisioning and Updating Configuration

One of the DevOps’ models to handle configuration updates consists of provisioning a brand new environment from scratch and completely discarding the old one (think container images). This implies a reliable management of your data lifecycle. In our particular case of Nexus’s Repository Manager, this consists of several gigs of uploaded/proxied artifacts, some audit logs, and OrientDB blobs containing the configuration. Therefore, depending on one’s environment constraints, it can make sense to be able to update the configuration of an already-provisioned Nexus instance. The declarative nature of Ansible’s core instructions is inline with this purpose, but any custom logic written in a role should be idempotent, take the “create or maybe update” path into account.

One must also note that some parts of the Nexus configuration cannot be updated. Some examples include:

  • the settings related to BlobStores
  • the admin password if you ever loose the current one (update : or maybe through this way)

How to make Nexus’s Groovy API Fit Well with Ansible

The basic steps of the installation are pretty straightforward and can all be written using simple Ansible core modules:

  • download and unpack the archive
  • create a system user/group
  • create a systemd service

(these steps are in tasks/nexus_install.yml)

And then comes the surprise: Nexus configuration is not available in a simple text file format which can be edited with the help of simple Ansible instructions. It is stored in an embedded OrientDB database that must not be altered directly. The documented way to setup Nexus is either through its web user interface, or through its Integration API.

The way the Integration API works is as follows:

  1. Write a Groovy script that handles your configuration change;
  2. Upload it to Nexus with an HTTP PUT request, creating a REST resource for this script;
  3. Call the script through its HTTP GET/POST resource.

URI Module to the Rescue!

Ansible’s uri module makes HTTP requests, providing automation to all of this.

The first step is to upload the Groovy script on Nexus. Note that the script may already be there. Therefore, on re-runs of the playbook, we try to delete it before taking any action, just in case:

Through tasks/declare_script_each.yml, follow on:

  ---
  - name: Removing (potential) previously declared Groovy script {{</span> item }}
    uri:
      url: "http://localhost:8081/service/siesta/rest/v1/script/{{ item }}"
      user: 'admin'
      password: "{{ current_nexus_admin_password }}"
      method: DELETE
      force_basic_auth: yes
      status_code: 204,404

  - name: Declaring Groovy script {{ item }}
    uri:
      url: "http://localhost:8081/service/siesta/rest/v1/script"
      user: 'admin'
      password: "{{ current_nexus_admin_password }}"
      body_format: json
      method: POST
      force_basic_auth: yes
      status_code: 204
      body:
        name: "{{ item }}"
        type: 'groovy'
        content: "{{ lookup('template', 'groovy/' + item + '.groovy') }}"

The HTTP requests are executed from inside the target host, which is why localhost is used here. force_basic_auth: yes makes the HTTP client not wait for a 401 before providing credentials, as Nexus immediately replies with 403 when no credentials are passed. status_code is the expected HTTP status replied by Nexus. Since the Groovy script may not necessarily exist at that point, we must also accept the 404 status code.

The next step is to call the Groovy script that has been created through the previous HTTP call. Most of the scripts will take some parameters as input (e.g. create user <x>), and this is where Ansible and Groovy will help. Both coming from the ages of REST things, they can speak and understand JSON fluently.

On the Groovy script side :

import groovy.json.JsonSlurper
parsed_args = new JsonSlurper().parseText(args)
security.setAnonymousAccess(Boolean.valueOf(parsed_args.anonymous_access))

And to call this script from Ansible passing arguments:

  - include: call_script.yml
    vars:
      script_name: setup_anonymous_access
      args: # this structure will be parsed by the groovy JsonSlurper above
        anonymous_access: true

with call_script.yml:

  ---
  - name: Calling Groovy script {{ script_name }}
    uri:
      url: "http://localhost:8081/service/siesta/rest/v1/script/{{ script_name }}/run"
      user: 'admin'
      password: "{{ current_nexus_admin_password }}"
      headers:
        Content-Type: "text/plain"
      method: POST
      status_code: 200,204
      force_basic_auth: yes
      body: "{{ args | to_json }}"

This allows us to cleanly pass structured parameters from Ansible to the Groovy scripts, keeping the objects’ structure, arrays and basic types.

Nexus Groovy Scripts Development Tips and Tricks

Here are some hints that can help a developer while working on the Groovy scripts.

Have a Classpath Setup in Your IDE

As described in the Nexus documentation, having Nexus scripting in your IDE’s classpath can really help you work. If you automate the Nexus setup as much as possible, you will inevitably stumble against some undocumented internal APIs. Additionally, some parts of the API do not have any source available (e.g. LDAP). In such cases, a decompiler can be useful.

Since our role on Github uses Maven with the all the necessary dependencies, you can simply open it with IntelliJ and edit the scripts in files/groovy.

Scripting API Entry Points

As documented, there are four implicit entry points to access Nexus internals from your script:

  • core
  • repository
  • blobStore
  • security

Those are useful for simple operations, but for anything more complicated you will need to resolve services more in-depth:

  • through indirection from the main entry points: blobStore.getBlobStoreManager()
  • directly by resolving an inner @Singleton from container context: container.lookup(DefaultCapabilityRegistry.class.getName())

Take Examples from Nexus’s Source Code

Some parts of Nexus (7.4%, according to Github) are also written using Groovy, containing lots of nice code examples: CoreApiImpl.groovy .

Creating HTTP requests from the configuration web interface (AJAX requests) also provides some hints about the expected data structures or parameters or values of some settings.

Last but not least, setting up a remote debugger from your IDE to a live Nexus instance can help, since there are lots of places where a very generic data structure is used (like Map<String, Object>) and only runtime inspection can quickly tell the actual needed types.

Detailed Examples

Here are some commented examples of Groovy scripts taken from the Ansible role

Setting up a Capability

Capabilities are features of Nexus that can be configured using a unified user interface. In our case, this covers:

  1. anonymous access
  2. base public URL
  3. branding (custom HTML header/footer).

Instructions:


    import groovy.json.JsonSlurper
    import org.sonatype.nexus.capability.CapabilityReference
    import org.sonatype.nexus.capability.CapabilityType
    import org.sonatype.nexus.internal.capability.DefaultCapabilityReference
    import org.sonatype.nexus.internal.capability.DefaultCapabilityRegistry

    // unmarshall the parameters as JSON
    parsed_args = new JsonSlurper().parseText(args)

    // Type casts, JSON serialization insists on keeping those as 'boolean'
    parsed_args.capability_properties['headerEnabled'] = parsed_args.capability_properties['headerEnabled'].toString()
    parsed_args.capability_properties['footerEnabled'] = parsed_args.capability_properties['footerEnabled'].toString()

    // Resolve a @Singleton from the container context
    def capabilityRegistry = container.lookup(DefaultCapabilityRegistry.class.getName())
    def capabilityType = CapabilityType.capabilityType(parsed_args.capability_typeId)

    // Try to find an existing capability to update it
    DefaultCapabilityReference existing = capabilityRegistry.all.find {
        CapabilityReference capabilityReference -&amp;amp;amp;amp;amp;amp;amp;gt;
            capabilityReference.context().descriptor().type() == capabilityType
    }

    // update
    if (existing) {
        log.info(parsed_args.typeId + ' capability updated to: {}',
                capabilityRegistry.update(existing.id(), existing.active, existing.notes(), parsed_args.capability_properties).toString()
        )
    } else { // or create
        log.info(parsed_args.typeId + ' capability created as: {}', capabilityRegistry.
                add(capabilityType, true, 'configured through api', parsed_args.capability_properties).toString()
        )
    }

Setting up a Maven Repository Proxy

    import groovy.json.JsonSlurper
    import org.sonatype.nexus.repository.config.Configuration

    // unmarshall the parameters as JSON
    parsed_args = new JsonSlurper().parseText(args)

    // The two following data structures are good examples of things to look for via runtime inspection
    // either in client Ajax calls or breakpoints in a live server

    authentication = parsed_args.remote_username == null ? null : [
            type: 'username',
            username: parsed_args.remote_username,
            password: parsed_args.remote_password
    ]

    configuration = new Configuration(
            repositoryName: parsed_args.name,
            recipeName: 'maven2-proxy',
            online: true,
            attributes: [
                    maven  : [
                            versionPolicy: parsed_args.version_policy.toUpperCase(),
                            layoutPolicy : parsed_args.layout_policy.toUpperCase()
                    ],
                    proxy  : [
                            remoteUrl: parsed_args.remote_url,
                            contentMaxAge: 1440.0,
                            metadataMaxAge: 1440.0
                    ],
                    httpclient: [
                            blocked: false,
                            autoBlock: true,
                            authentication: authentication,
                            connection: [
                                    useTrustStore: false
                            ]
                    ],
                    storage: [
                            blobStoreName: parsed_args.blob_store,
                            strictContentTypeValidation: Boolean.valueOf(parsed_args.strict_content_validation)
                    ],
                    negativeCache: [
                            enabled: true,
                            timeToLive: 1440.0
                    ]
            ]
    )

    // try to find an existing repository to update
    def existingRepository = repository.getRepositoryManager().get(parsed_args.name)

    if (existingRepository != null) {
        // repositories need to be stopped before any configuration change
        existingRepository.stop()

        // the blobStore part cannot be updated, so we keep the existing value
        configuration.attributes['storage']['blobStoreName'] = existingRepository.configuration.attributes['storage']['blobStoreName']
        existingRepository.update(configuration)

        // re-enable the repo
        existingRepository.start()
    } else {
        repository.getRepositoryManager().create(configuration)
    }

Setting up a Role

    import groovy.json.JsonSlurper
    import org.sonatype.nexus.security.user.UserManager
    import org.sonatype.nexus.security.role.NoSuchRoleException

    // unmarshall the parameters as JSON
    parsed_args = new JsonSlurper().parseText(args)

    // some indirect way to retrieve the service we need
    authManager = security.getSecuritySystem().getAuthorizationManager(UserManager.DEFAULT_SOURCE)

    // Try to locate an existing role to update
    def existingRole = null

    try {
        existingRole = authManager.getRole(parsed_args.id)
    } catch (NoSuchRoleException ignored) {
        // could not find role
    }

    // Collection-type cast in groovy, here from String[] to Set&amp;amp;amp;amp;amp;amp;amp;lt;String&amp;amp;amp;amp;amp;amp;amp;gt;
    privileges = (parsed_args.privileges == null ? new HashSet() : parsed_args.privileges.toSet())
    roles = (parsed_args.roles == null ? new HashSet() : parsed_args.roles.toSet())

    if (existingRole != null) {
        existingRole.setName(parsed_args.name)
        existingRole.setDescription(parsed_args.description)
        existingRole.setPrivileges(privileges)
        existingRole.setRoles(roles)
        authManager.updateRole(existingRole)
    } else {
        // Another collection-type cast, from Set&amp;amp;amp;amp;amp;amp;amp;lt;String&amp;amp;amp;amp;amp;amp;amp;gt; to List&amp;amp;amp;amp;amp;amp;amp;lt;String&amp;amp;amp;amp;amp;amp;amp;gt;
        security.addRole(parsed_args.id, parsed_args.name, parsed_args.description, privileges.toList(), roles.toList())
    }

The resulting role is available at Ansible Galaxy and on Github. It features the setup of :

  • Downloading and unpacking of Nexus
  • SystemD service unit
  • (optional) SSL-enabled apache reverse proxy
  • Admin password
  • LDAP
  • Privileges and roles
  • Local users
  • Blobstores
  • All types of repos
  • Base URL
  • Branding (custom HTML header & footer)
  • Automated jobs

 

Mastering the Thumbnail Generator with Liferay 7 CE and DXP

The Thumbnail Generator aims to improve and facilitate the generation of thumbnails provided by Liferay.

This plugin was created during a project requiring a large number of thumbnails with precise dimensions in order to minimize the loading time of the web pages. Currently, Liferay is only capable of generating two different sizes of thumbnails, when a new image is uploaded on an application (using the dl.file.entry.thumbnail.custom* settings of portal-ext.proprties). This new plugin, however, allows you to have full control over the number of thumbnails created as well as the way they are generated.

This article is structured as follows. After briefly describing the main components of this plugin, I will explain how to configure it in order to manage an unlimited number of thumbnails with Liferay.

I. Describing the Plugin Components

The Listeners
The Thumbnail Generator uses two Model Listeners to listen to ”persistence events” like the creation, modification and deletion of documents in Liferay application. A document can match any file’s type (text, image, video, pdf, …). Later, you will learn how to configure the plugins in order to process only relevant documents.

The first Listener listens for the creation and modification of a document, then it creates or updates the document’s thumbnails. The second listens for the deletion of a document, and deletes the thumbnails associated with this document in the aftermath.

The Servlet Filter
The Servlet Filter intercepts all requests calling for a document of the application and performs a series of validation before returning a thumbnail in response. It will first analyze the parameters of the query in order to know if a thumbnail is requested. Next, the filter is going to verify that the thumbnail does exist in order to finally return it to the author of the request. If one of these checks fails, the query will be ignored by the filter and it will follow its normal course – i.e. returning the original document requested.

The ThumbnailService
Lastly, the ThumbnailService handles the creation/deletion of the thumbnails and organizes them in the storage system of Liferay, using the plugin’s configuration.

II. Using the Plugins

The use of the Thumbnail Generator entails configuring the plugins and retrieving the thumbnails.

Configuration
The Thumbnail Generator’s configuration page (Menu => Control Panel => Configuration => System Settings => Thumbnail Configuration) allows you to define two options:

  • The format of the files that will be processed by the plugin.
    For example, to restrict the creation of thumbnails for JPG and PNG files, simply add these formats to the configuration and all the other files will not be taken into account by the plugin.
  • The command lines that will generate the thumbnails.
    In order to define a thumbnail and to generate it, you need to add a line in the configuration with the following syntax : ‘name:command‘. The name will later provide access to this thumbnail, the command corresponds to the command line that will generate the thumbnail (see ImageMagick’s documentation to explore all possible options). For example : ‘img_480:convert ${inputFile} -resize 480×270 ${outputFile}‘ will generate a thumbnail of dimension 480×270 and that will be retrievable through its name « img_480 ».

Thumbnail Generator configuration page

In the above screenshot, three different thumbnails will be created for each JPG and PNG files uploaded in the application.

The plugin’s configuration not only allows the user to control the number of thumbnails to be generated, but also the way in which they are created. In this scenario, the ‘convert command’ comes from the powerful image editing library ImageMagick. Instead of this command, we could have used any other commands executable on the machine hosting the Liferay application.

Thumbnails’ Retrieval
Once the plugin is deployed and configured, it is ready for use. Thumbnails will be automatically generated each time a document is uploaded into your application. In order to retrieve the thumbnail of the document, you just have to add the parameter “thumb={thumbnailName}” in the URL using this document.

An Example of Thumbnail Retrieval Process

  • The URL of a document (test.jpg) on a local instance of Liferay looks like this : http://localhost:8080/documents/20147/0/test.jpg/0d72d709-3e48-24b3-3fe6-e39a3c528725?version=1.0&t=1494431839298&imagePreview=1
  • The URL of a thumbnail associated to this document, named img_480, can be called this way : http://localhost:8080/documents/20147/0/test.jpg/0d72d709-3e48-24b3-3fe6-e39a3c528725?version=1.0&t=1494431839298&imagePreview=1&thumb=img_480

III. Administration

In order to give more control to the user in the management of this module, an administration page (your site > Configuration >  Thumbnails administration) has been created allowing you to perform some actions on the thumbnails:

  • Re-generate all the thumbnails
  • Delete all the thumbnails
  • Delete orphans thumbnails (Which are no longer linked to any documents but are still present due to a change in the configuration)

Thumbnail Generator administration

In conclusion, this brief tutorial introduces to you the Liferay’s utility app called Thumbnail Generator and describes how to use, configure, retrieve the thumbnails and administer the plugin. Should you have any further questions or comments please contact us.

Ring Stable Version Released: Ring 1.0 Liberté, Égalité, Fraternité


July
21, 2017Savoir-faire Linux releases the stable version of Ring:  Ring 1.0 – Liberté, Égalité, Fraternité. Ring is a free/libre and universal communication platform that preserves the users’ privacy and freedoms. It is a GNU package. It runs on multiple platforms; and, it can be used for texting, calls, and video chats more privately, more securely, and more reliably.


About Ring

Ring is a fully distributed system based on OpenDHT technology and Ethereum Blockchain. It means, it does not need any central authority, an enterprise or even a server to function. Therefore, it avoids keeping centralized registries of users and storing their personal data. In addition, Ring is based on standard security protocols and end-to-end encryption. Therefore, it prevents decryption of communications over the network and consequently offering a high level of privacy and confidentiality.

Key Functionalities and Features

– Encrypted Audio/VideoHD/InstantMessaging Communications (ICE, SIP, TLS)
– Screen Sharing and Conferencing (Win32 and GNU/Linux)
– Support of Ethereum Blockchain as Distributed Public Users’ Database
– Distributed Communication Platform (OpenDHT)
– Platform Support on GNU/Linux, Windows UWP (Windows 10 and Surface), macOS (10.10+) and Android (4.0+)
– Distributed under GPLv3+ License
– Parts of Ring can be used as a building block in any Internet of Things (IoT) project

Ring: An Impactful and Inspirational Social Innovation

Ring is based on the state-of-the-art technologies (OpenDHT) and follows strict ethical guidelines. Together, a mix of free software technologies, and ethical rules offers end-users: leading edge privacy and anonymity, confidentiality as well as security of conversations. In addition, its stable connectivity and innovative standard functionalities over multitude of platforms make it a suitable choice for an everyday communication. 

Important Links
> Download Ring
> Contribute to Ring
> Technical documentation

How to Become a Contributor to WordPress? Baby Steps… To Big Dreams!

Are you passionate about the Web and Free Software Movement? Are you looking for an opportunity to play your part? Perhaps you’ve considered contributing to the WordPress project? This tutorial is for you! Our free software developers have contributed to the core of the application and now they’d love to introduce you to the development process of the most popular on-line publishing platform in the world. Cheer up and follow these steps to get started!

Subscribe
You can begin your journey by subscribing to a variety of platforms used by contributers. This helps you establish a connection with the community and remain in touch.

WordPress
Subscribing to WordPress is a must. WordPress.org serves, among other things, to document procedures and publish information related to its development platform. This will be your main reference, so subscribe and log in.

Once you are connected, we invite you to consult the section of the website concerning Development and to get to know the different teams and their missions. This is also a great opportunity to subscribe to various newsletters if you wish to follow a particular stream of development (marketing, modules, themes, documentation, etc.)

Slack
People discuss their contributions via the Slack collaborative communication platform. They hold meetings, disseminate information and help users reach their contribution objectives. On Slack, you will find all the influential developers in the WordPress community, it is the ideal place to ask them questions!

Please read the subscription documentation carefully. The procedure can sometimes be confusing. If you are having trouble signing in to your WordPress.org account, visit the subscription page and the new instructions will show up. You can then sign in using this link: https://wordpress.slack.com/.

Trac
Your last step before contributing! Check out the Trac ticket manager. Every change in WordPress is documented here. The main developers use this tool to approve and integrate changes to the core. To ensure effective, accurate and coordinated development, using documentation is mandatory. Now we can get started with developing for WordPress…

Following the Best Practices
Let the fun begin! Before writing code, you will need to integrate the project’s best practices and development standards. Some documents will be more useful to you than others. We suggest that you focus on these sections: Core Contributor Handbook, Best Practices, and Writing Patches. For PHP developers, you will also be interested in the PHP Coding Standards and Core API documentation.

The Environment
The majority of developers use Varying Vagrant Vagrants (VVV), which runs under all operating systems. VVV is an open source Vagrant configuration focused on WordPress development. It is mostly used to develop themes, modules, and plugins as well as for contributions to WordPress core. It may be a bit complex to install optional Vagrant modules, so if you are using a Linux environment, just make sure you have the “build-essential” and “libc6-dev” libraries before you get started. Feel free to work with other tools as well. VVV is not the only choice you have. But if you choose any other tools, please do not forget to report your developments on WordPress’ core code repository, to track the testing and progress of your contributions!

Here is an example of installing a development environment using VagrantPress and Git deployed with Ubuntu.

git clone https://github.com/vagrantpress/vagrantpress.git
cd vagrantpress
vagrant up
rm -fr tests
git clone git://develop.git.wordpress.org/ tests
vagrant provision

SVN and Git
You have probably noticed that the code repository uses SVN. If you wish to contribute to the heart, we strongly recommend using it. But there is no obligation, it is also possible to go through Git. You will find the documents you need to use for these two versioning systems in the following libraries: How to Use Subversion for developers, plugin, codex for SVN and Using Git for the second.

CSS and Javascript
WordPress compresses some resources. To enable you to work, you must disable this function in the “wp-config.php”. Add “define (‘SCRIPT_DEBUG’, true);”.

Code Validation
WordPress code standards are most likely different from those you are used to. A code format checker can provide great help. Use PHP_CodeSniffer with the WordPress Coding Standards. You can also read the WordPress style guidelines for detailed installation instructions.

Test-based Contribution
Did you know that you do not have to be a seasoned developer to contribute to WordPress? For example, testing is a good way to participate in development while learning. Trac lists the corrections to be tested. If you are starting out, work first on non-urgent corrections.

Baby Steps… To Big Dreams!
Yes, contributing to a free software project is indeed a huge investment of your time. Reading, setting things up, making configurations, downloading, etc.

However, once you’ve passed the first steps and made your first few contributions, you will officially be a free software contributor! Now take this chance to make your first baby steps and realize your big dream of becoming a free software developer!

 

Liferay DXP on Azure: A Practical Case Study

When it comes to deploying a common Java server on a common cloud infrastructure, a range of possibilities crosses one’s mind. This article presents some feedback on a particular, real-life, case of deploying Liferay Digital Experience (DXP, aka. Liferay 7 EE) on Microsoft’s Azure cloud platform.

Initial Shopping List

  • Liferay DXP, with Enterprise Subscription, fitting in the compatibility matrix
  • Process of deploying Liferay DXP on Azure
  • Reliance on SaaS solutions as much as possible
  • Use of infrastructure & platform provisioned by Ansible

The Happy Path : Azure SaaS Building Blocks

In this process, we consistently use Azure’s SaaS platform. We set up the following three (3) services:

Service I : Azure SQL database

Our team configures Tomcat JDBC using driver sqljdbc42.jar from https://www.microsoft.com/en-us/download/details.aspx?displaylang=en&id=11774 .

Instructions :

/etc/tomcat8/Catalina/localhost/ROOT.xml :

<Resource
    name="jdbc/LiferayPool"
    auth="Container"
    type="javax.sql.DataSource"
    driverClassName="com.microsoft.sqlserver.jdbc.SQLServerDriver"
    url="jdbc:sqlserver://******.database.windows.net:1433;databaseName=*******"
    username="liferay-dev"
    password="*******"
    maxActive="100"
    maxIdle="30"
    maxWait="10000"
>

(note : there seems to be a new MS-SQL JDBC release going on github)

Service II : Azure files

We set up the Liferay document store library using the Advanced File System Store mounted locally on Liferay’s VM. For the mounting process we use CIFS from an Azure file share.

Instructions :

/etc/fstab :

//<storage account name>.file.core.windows.net/<file share name> /var/lib/liferay/data-remote cifs vers=3.0,username=<storage account name>,password=<storage account key>,uid=tomcat8,gid=tomcat8 0 0

/var/lib/liferay/osgi/modules/com.liferay.portal.store.file.system.configuration.AdvancedFileSystemStoreConfiguration.cfg :

rootDir=/var/lib/liferay/data-remote


Service III : Azure Application Gateway

We use an Application Gateway to handle a)SSL termination ; and b)load-balancing of the web traffic between two Liferay instances (round robin only).

In order to convert the SSL certificate + chain + key to Azure’s .pfx bundle, use :

$> openssl pkcs12 -export -out <new pfx filename>.pfx -inkey <key filename>.key -in <certificate with chain filename>.crt -passout pass:<new pfx password>

The Plain Old Virtual Machine Part

Liferay DXP itself is installed on an Ubuntu Virtual Machine. This matches the recommended installation scheme for a production instance, which is deploying Liferay in the system tomcat8 installation. Liferay Hot Fixes and Service Packs can also be installed in this context, using Liferay’s patching-tool utility.

The Unexpected : Boundaries of Liferay’s Compatibility Matrix

Liferay DXP uses Elastic Search by default as its indexing and search provider. To stay within the compatibility matrix on production, Elastic Search needs to be hosted on a separate, dedicated Java Virtual Machine (JVM) (as opposed to the default development scheme which embeds ES alongside Liferay in a single JVM). Azure does not propose any Elastic Search as a service (managing your ES cluster is up to you). Furthermore, in order to secure a connection from Liferay DXP to a remote Elastic instance (e.g. Elastic cloud), you will need the extra “Liferay Elastic Shield Integration” marketplace item, purchasable as part of the Enterprise Search Add-On Subscription. This extra item would allow you to use Elastic Shield, though you cannot stay in the compatibility matrix today using Elastic Cloud, as its ES version is more recent than the one officially supported by Liferay.

Following this approach, we can install a 2.2 Elastic Search instance on a VM accessible from Liferay without having any need for Elastic Shield (on the same machine or internal network). Figure 1 below presents the summary of the global scheme explained here.

Figure 1. The Summary of the Global Scheme

Based on Figure 1 above, and the detailed explanations we can map three alternatives which can be evaluated and tested in future. First, it seems that the pre-packaged Liferay appliance allows for a single-click deployment on Azure but only for CE edition only. This is one avenue for further discovery. Second, we need to explore and evaluate the feasibility of pushing various Docker Liferay images to Azure Container Service and identify which ones do not offer this possibility. Third, is it possible to deploy Liferay as a custom Azure Web App? This is yet another issue to pursue further. For more insights on deploying Liferay Digital Experience Platform (DXP) on Microsoft’s Azure cloud platform stay tuned…