GAP logo

GAP calls itself a Swiss army knife for AEM related automation. In the case of instance setup, it is more like a doctor with AEM specialization (aemologist). GAP knows how to diagnose AEM state, what AEM can handle and when. It is patient when it is needed and demanding when it is possible to gain better performance.

This article focuses solely on how to configure GAP from scratch for AEM instance provisioning on Java 8 (it doesn't touch application development using GAP). The source code for this article can be found on GitHub AEM instance setup.

As a next step, we encourage you to look at the Gradle AEM Multi template project, which is our recommendation on how to use GAP in multi-module AEM project.

Gradle project skeleton

For start let's generate Gradle Wrapper in the project directory for the current version of Gradle (at the moment of writing 6.6.1):

gradle wrapper --gradle-version 6.6.1 --distribution-type all

Gradle properties file

Create gradle.properties file and paste the following configuration:

# AEM distribution
localInstance.quickstart.jarUrl=smb://smb-host/aem/6.5.0/cq-quickstart-6.5.0.jar
localInstance.quickstart.licenseUrl=/Users/user.name/aem/6.5.0/license.properties

fileTransfer.user=user.name
fileTransfer.password=
fileTransfer.domain=COMPANY_DOMAIN

# Instance description
instance.local-author.httpUrl=http://localhost:4502
instance.local-author.type=local
instance.local-author.runModes=local,nosamplecontent
instance.local-author.jvmOpts=-server -Xmx2048m -XX:MaxPermSize=512M -Djava.awt.headless=true

instance.local-publish.httpUrl=http://localhost:4503
instance.local-publish.type=local
instance.local-publish.runModes=local,nosamplecontent
instance.local-publish.jvmOpts=-server -Xmx2048m -XX:MaxPermSize=512M -Djava.awt.headless=true

AEM distribution

To setup AEM from scratch, we need to specify direct URIs to distributions (file paths, HTTP, FTP or SMB URIs). If authorization is required to access those files please also provide fileTransfer credentials (user, password and domain for SMB protocol).

Instance configuration

Knowing where to find AEM distribution, GAP needs a declarative description of how to create our instances (author and publish in this case). We need to provide httpUrl (startup port will be derived from it), runModes and jvmOpts (passed to AEM quick-start JAR), and additionally type of an instance. We use local to tell GAP, that it will be responsible for creating those instances. If type would be set to remote, then GAP would expect those instances to be up and running.

Quite a few options! Happily, we need to configure this only once.

Notice: It is also possible to configure all of this in build.gradle.kts file (see docs).

Configure setup/provisioning process

Create build.gradle.kts file and add there:

plugins {
    id("com.cognifide.aem.instance.local") version "14.4.1"
}

aem {
    // here is where specific configuration will come
}

Since GAP is a set of plugins, we need to decide which one to apply here. To setup local instances and interact with remote ones we need the Local Instance Plugin. After applying it, we have access to configuration DSL under aem namespace. Generally, that's it. Having this configuration and running sh gradlew instanceSetup would create two new instances. However, before you run it, let's add few useful steps to the setup.

Enable CRXDE

OOTB AEM instance runs with disabled CRXDE Lite. To enable it we need to add OSGi configuration. This is a perfect example of adding a step to instanceProvision task (place it in the aem.instance.provisioner section of your build.gradle.kts):

aem {
    instance {
        provisioner {
            step("enable-crxde") {
                description.set("Enables CRX DE")
                condition { once() && instance.author }
                action {
                    sync {
                        osgiFramework.configure(
                                "org.apache.sling.jcr.davex.impl.servlets.SlingDavExServlet",
                                mapOf("alias" to "/crx/server")
                        )
                    }
                }
            }
        }
    }
}

You can see some useful switches there. Using the condition you can filter instances where this step will be applied. In the example above, we enabled CRXDE for the author only. Also, it is possible to specify how frequently we want this action to be applied. If once is your choice, then GAP will add a "guard" node on an instance, to remember that this has already been applied.

Fortunately, GAP already knows how to enable CRXDE, so you can simplify the above code snippet to this:

aem {
    instance {
        provisioner {
            enableCrxDe()
        }
    }
}

Satisfy packages

Now, let's have ACS AEM Commons, Groovy Console and Kotlin language support bundles pre-installed on the instance. It also could be done by simply configuring provisioner by the snippet below:

aem {
    instance {
        provisioner {
            enableCrxDe()
            deployPackage("https://github.com/Adobe-Consulting-Services/acs-aem-commons/releases/download/acs-aem-commons-4.0.0/acs-aem-commons-content-4.0.0-min.zip")
            deployPackage("org.jetbrains.kotlin:kotlin-osgi-bundle:1.4.10")
            deployPackage("https://github.com/icfnext/aem-groovy-console/releases/download/13.0.0/aem-groovy-console-13.0.0.zip")
        }
    }
}

Instance setup

OK! Since instance configuration is done, it's time for running the command:

sh gradlew instanceSetup

Instance(s) created
Which: local-author, local-publish
<==-----------> 20% EXECUTING [2m 5s]
> :instanceUp > \ Checking # local-author: Bundles stable (124/578=21.45%) | local-publish: Bundles stable (151/578=26.12%)

Let's see what is happening. In our project folder, we can see now .gradle/aem/localInstance directory. It contains all the AEM author and publish files. Frankly speaking, there is a lot more happening behind the scene. GAP is altering startup scripts, monitoring startup process, etc.

When an instance is up, GAP will install packages and perform provisioning steps.

Now we need to wait a couple of minutes (~10) - it is a good time to describe how GAP approaches instance stability.

Instance stability & events monitoring

GAP never performs fire and forget actions. It always awaits instance stability after installing new packages, performing configuration steps, etc. It performs a number of checks, ensuring all bundles and components are active.

The substantial feature here is event monitoring. Installation of a package, or adding a specific configuration on an instance can trigger a massive amount of events that will make AEM unresponsive. GAP is patient and always waits for those events to be processed before proceeding to the next step. Thanks to this, you can always be sure that when GAP is done with package installation, your instance is ready for further checks.

What is more, you can rely on GAP for production deployments. GAP can make you confident, that AEM is stable and responsive, and you can proceed with refreshing the dispatcher cache, etc.

Interactive logs monitoring

Another cool feature is AEM log tailing. Instead of manually looking for error entries in logs, GAP could do that automatically and interactively.

Simply run:

sh gradlew instanceTail

and keep it running in the background. Since now, all errors occurring on configured AEM instance will be immediately reported within OS notification/balloon. Full log, formatted and with timestamps calculated to your computer time zone (when tailing remote instances), is also printed to the console:

[local-author ]  2019-12-31 13:57:31.660  INFO   [0:0:0:0:0:0:0:1 [1577797051656] GET /etc.clientlibs/clientlibs/granite/typekit/resources/ruf7eed/c/ruf7eed-d.css HTTP/1.1]  com.company.example.aem.sites.LoggingFilter Request for '/etc', with selector 'null'
[local-author ]  2019-12-31 13:57:31.770  INFO   [0:0:0:0:0:0:0:1 [1577797051573] GET /etc.clientlibs/clientlibs/granite/typekit/resources/ruf7eed/c/ruf7eed-d.css HTTP/1.1]  com.company.example.aem.sites.LoggingFilter Request for '/etc', with selector 'null'

Notice that this tool could observe an unlimited number of instances at once. No matter if instances are local or remote. The only requirement is to have them accessible over HTTP protocol (no SSH required) so that GAP could request for new log entries automatically.

Filtering logs

It is very important to listen only to the logs which you are interested in. Otherwise, you would get flooded with a cannonade of unimportant notifications. To filter logs simply add and edit src/aem/instance/tail/incidentFilter.txt file and add there log message or only a part of it. Logs that would match those lines will get filtered out. You can use wildcards as well, try:

org.apache.felix.metatype Missing element * in element OCD

Other instance control tasks

Let's review all the tasks available: sh gradlew tasks

AEM tasks
---------
instanceAwait - Await for healthy condition of all AEM instances.
instanceBackup - Turns off local instance(s), archives to ZIP file, then turns on again.
instanceCreate - Creates local AEM instance(s).
instanceDestroy - Destroys local AEM instance(s).
instanceDown - Turns off local AEM instance(s).
instanceGroovyEval - Evaluate Groovy script(s) on instance(s).
instanceKill - Kill local AEM instance process(es)
instanceProvision - Configures instances only in concrete circumstances (only once, after some time etc)
instanceRcp - Copy JCR content from one instance to another.
instanceReload - Reloads all AEM instance(s).
instanceResetup - Destroys then sets up local AEM instance(s).
instanceResolve - Resolves instance files from remote sources before running other tasks
instanceRestart - Turns off then on local AEM instance(s).
instanceSetup - Creates and turns on local AEM instance(s) with satisfied dependencies and application built.
instanceStatus - Prints status of AEM instances and installed packages.
instanceTail - Tails logs from all configured instances (local & remote) and notifies about unknown errors.
instanceUp - Turns on local AEM instance(s).

Wow, quite a few! Feel free to play with them! You can find full docs on Gradle AEM Plugin page.

Summary

Setting up your instances with GAP might be an interesting alternative to other tools like Chef. Ease of configuration connected with robust stability checks and log tailing can make the difference in your day to day work.