DISCLAIMER: This hook should be used sparsely for very limited use cases only. Almost all OSGi configurations should be bound to runmodes as well-established mechanism to configure different environments. However reasonable use cases are:
- Replication Agents (if the set of potential target environments is unlimited/unknown)
- Cloud Configurations with differing values (e.g. URLs) between different environments
- OSGi configurations that differ per env (e.g. for a AEM communities user sync)
- Sensitive production passwords (in OSGi configurations or cloud services) that have to be deployed automatically (this can be mitigated by using the CryptoService and a well-defined master key, however if the master key leaks all passwords can be decrypted)
For OSGi configurations, Felix recently introduced the configadmin plugin interpolation that allows for env variables substitution on framework level, however there is no out-of-the-box platform solution to apply env variables to content.
Replaces variables in content packages for files (e.g. useful for OSGi configurations) and node properties (e.g. useful for replication agents).
Variables can be in form ${myVar}
or ${myVar:defaultVal}
- if the var can not be found in any sources, for the first example ${myVar}
remains as is, for the second example defaultVal
is used.
Additionally to nodes that explictly configured to be adjusted, any nodes that end with .TEMPLATE
will be copied to the same name without .TEMPLATE
(e.g. /etc/path/to/configfile/com.example.MyService.config.TEMPLATE
will be copied to /etc/path/to/configfile/com.example.MyService.config
with the values replaced). To avoid that a configuration is deleted upon regular package installation, the target node (without .TEMPLATE
) has to be excluded via exclude rule of filter statement.
Example:
- Node to be added:
/etc/replication/agents.author/publish
- Node contained in the package:
/etc/replication/agents.author/publish/jcr:content.TEMPLATE
(don't include/etc/replication/agents.author/publish/jcr:content
!) - Filter rule with exclude (to ensure it is not deleted because it's not in package):
/etc/replication/agents.author/publish
\- exclude /etc/replication/agents.author/publish/jcr:content
Alternatively, the vault package property applySystemEnvForPaths
can be given to explicitly list properties and files where the replacement shall take place (opposed to use the automatic .TEMPLATE
mechanism described above):
/etc/path/to/node/jcr:content@testValue,
/etc/path/to/node/jcr:content@testValueOther,
/etc/path/to/configfile/com.example.MyService.config
Multiple values can be given separated by whitespace and/or comma. This approach has the downside that nodes are "double-saved" (first one on package installation itself, second one on install hook phase INSTALLED
with replaced parameters).
The package property applySystemEnvSources
is used to defined sources (default: SystemProperties,JCR,OsEnvVars
). For each variable each source is asked, the first source that resonds with a value will be used (the installation log in Package Manager clearly logs where variable values come from).
Will use the immutable system environment as provided by the OS. All values with .
have to be set with _
as dots are not allowed as environment variables (e.g. my.test.var
as set in package has to be set as env variable my_test_var
).
Reads the node /etc/system-env
and uses all its properties to set the variables. This is particular useful to be used for CI/CD tools like Jenkins to easily set values via REST (Sling POST Servlet).
If the node /etc/system-env
does not exist, this source is ignored (no error is thrown).
NOTE: Obviously Jenkins could also post to the actual config locations and change them in place. However then Jenkins has to re-send a lot of boilerplate around the few values that are actually env-specific. Also the paths may be scattered all around the repo which makes the Jenkins-side unnecessarily complicated. Using apply-system-env-install-hook with values from /etc/system-env clearly separates "regular config" from system-env.
Configuration variables for a whole environment can be maintained in ZooKeeper and automatically be consumed by multiple AEM instances (e.g. author cluster + 4 publishers).
To configure ZooKeeper itself, the following JRE System Parameters have to be set (along with the source in the package):
applysysenv.zookeeper.hosts
(required): the ZooKeeper hosts (e.g.localhost:2181,localhost:2182,localhost:2183
applysysenv.zookeeper.path
(required): The path where the config resides, e.g./configs/aem-env.properties
.applysysenv.zookeeper.overrideSuffix
(optional): This is useful if some properties are different for only one particular instance (whileaem-env.properties
describes all instances of an environment) - e.g. for replication, if publishers have their own flush url,replication.publish.flushUrl@publish1
andreplication.publish.flushUrl@publish2
can be used (while e.g. publish1 would have the system propertyapplysysenv.zookeeper.overrideSuffix=publish1
set)
One way of loading a file into ZooKeeper is using zkCli.sh
:
zkCli.sh set /configs/aem-env.properties "`cat aem-env.properties`"
If the source ZooKeeper
is configured in package, but applysysenv.zookeeper.hosts
or applysysenv.zookeeper.path
is not set, a warning is logged an the source will not provide any values.
Also, for this source to work the zookeeper jar has to be put in crx-quickstart/install (or be installed via a different means).
Will use system properties to set variables. Mostly useful for manual intervention, hence first source in default setting of applySystemEnvSources
.
Ensure the hook ins copied into the package:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.0.2</version>
<executions>
<execution>
<id>copy-hook-into-package</id>
<phase>generate-resources</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>biz.netcentric.aem.sysenvtools</groupId>
<artifactId>apply-system-env-install-hook</artifactId>
<version>1.2.0</version>
</artifactItem>
</artifactItems>
<outputDirectory>${project.build.directory}/vault-work/META-INF/vault/hooks</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
Configure it via vault package properties:
<plugin>
<groupId>com.day.jcr.vault</groupId>
<artifactId>content-package-maven-plugin</artifactId>
<version>0.5.1</version>
<extensions>true</extensions>
<configuration>
<group>Netcentric</group>
<filterSource>src/main/META-INF/vault/filter.xml</filterSource>
<properties>
<applySystemEnvSources>SystemProperties,ZooKeeper,OsEnvVars</applySystemEnvSources>
<!-- paths to adjust, can be left out if '.TEMPLATE' paths are used -->
<applySystemEnvForPaths>
/etc/path/to/node/jcr:content@testValue,
/etc/path/to/node/jcr:content@testValueOther,
/etc/path/to/configfile/com.example.MyService.config
</applySystemEnvForPaths>
<!-- default is false -->
<failForMissingEnvVars>true</failForMissingEnvVars>
</properties>
<targetURL>http://${crx.host}:${crx.port}/crx/packmgr/service.jsp</targetURL>
</configuration>
</plugin>
The environment variables need to be set to the env of the AEM process (since the install hook runs there). To check on OS-level if the env was set correctly, the following command can be used:
sudo cat /proc/<pid>/environ | tr '\0' '\n' # if you know the pid
sudo cat /proc/$(pgrep -f quickstart.jar)/environ | tr '\0' '\n' # auto-query the pid
Also, on AEM/JRE level the Groovy Console can be used to show the environment as the JRE gets to see it by running the following simple script:
System.getenv().each{ println it }
return
When updating configuration values, normally they only become active upon manual or automatically triggered re-installation of configuration package that contains the apply-system-env-install-hook
.
However, when using the sources ZooKeeper
or JCR
it is possible to automatically reinstall the package containing the install hook.
This feature requires AEM 6.3.0 or higher
Install the bundle system-env-change-listener-x.x.x.jar
to AEM. The easiest way to do this is to drop it in crx-quickstart/install
.
Create a service user (e.g. sysenv-package-installer
) with permissions to install packages and create a user mapping as follows:
org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-sysenv-listener.config
user.mapping=["biz.netcentric.aem.sysenvtools.system-env-change-listener\=sysenv-package-installer"]