AEM Cloud Environment and Secret Variables

As part of this blog, we will try to get a complete understanding of OSGI configuration secret and environment-specific variables as part of AEM as a Cloud Service.

The blog will allow us to gain in-depth knowledge on the following things:

  • Why do we require OSGI configurations?
  • understanding of the old and New OSGI configuration patterns
  • Run modes
  • Types of OSGI configuration
  • Understanding Secret and Environment-Specific Variables
  • Variable Naming conventions and best practises
  • OSGI Configuration Implementation

AEM OSGI Services stands for Open Service Gateway Initiative. It is a Java class that can register as an OSGI service.

We can create an OSGI service with the help of the @Component annotation from the org.osgi.service.component.annotations package and register it as an OSGI service.

A deployed component on an AEM instance gets handled by SCR (Sling Component Runtime). Bundles communicate with each other with the help of services.

Using the .cfg.json format defined by the Apache Sling project, the OSGi configuration files are JSON-based.

Let’s first try to understand what OSGI configuration is all about and how it works.

Why we require OSGI Configurations

OSGI configurations allow us the flexibility to pick AEM instance-specific configurations. For example, as part of our AEM implementation, we are planning to have an Elastic Search or any other 3rd party API integration. For every DEV/QA/UAT/PROD AEM instance, we need a different Elastic Search instance and its API details such as API url, username, password, etc. OSGI configuration in actuality helps AEM instances uniquely identify AEM instance-specific Elastic search and 3rd party API details depending upon dev, QA, UAT, and production run modes.

Run Modes

Run mode allows us to declare environment-specific properties and values. AEM Run modes also allow us to configure an AEM instance depending on requirements and load environment-specific configurations.

For example, we are connecting with a third-party API to log in end users in Production. But, at the same time, we will require this third-party API to test functionalities in lower environments (DEV, QA, UAT, and Stage). Run mode helps us load environment-specific URLs and configurations.

AEM as a Cloud Service does not allow using run modes to install content for specific environments or services. If a development environment must be seeded with data or HTML that is not in the staging or production environments, Package Manager can be used.

AEM as a Cloud Service Runmodes are well defined based on the environment type and service. The supported run mode configurations are config, config.author, config.author.dev, config.author.rde, config.author.stage, config.author.prod, config.publish, config.publish.dev, config.publish.rde, config.publish.stage, config.publish.prod, config.dev, config.rde, config.stage, config.publish.prod, config.dev, config.rde, config.stage, config.prod,

The OSGI configuration that has the most matching run modes is used.

When developing locally, a run mode startup parameter, -r, is used to specify the run mode OSGI configuration.

Java -jar aem-sdk-quickstart-xxxx.x.xxx.xxxx-xxxx.jar -r publish,dev

Old OSGI Configurations Pattern

Earlier we use to have .cfg, .config and .xml as an extension for OSGI configuration file which used to hold content in XML / text format. Below is an example of one of the OOTB configuration file show below.

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
jcr:primaryType="sling:OsgiConfig"
resource.resolver.mapping="[/content/practice/&lt;/,/:/]"/>

New OSGI Configuration Pattern

As part of latest OSGI configuration we use .cfg.json as a file extension and content in JSON format as as shown below.

Below is an example of OOTB OSGI configuration having .cfg.json extension and JSON as content org.apache.sling.jcr.resource.internal.JcrResourceResolverFactoryImpl.cfg.json

{
"resource.resolver.default.vanity.redirect.status": [
"301"
],
"resource.resolver.map.location": "/etc/practice/publish/dev",
"resource.resolver.mapping": [
"/:/",
"/content/practice/:/",
"/content/websites/:/"
],
"resource.resolver.searchpath": [
"/apps",
"/libs",
"/apps/foundation/components/primary",
"/libs/foundation/components/primary"
],
"resource.resolver.vanity.precedence": true
}

Types of OSGI Configuration value

There are three varieties of OSGi configuration values that can be used with Adobe Experience Manager as a Cloud Service.

  1. Inline values, Hard-coded OSGi configuration which stored/push through Git. For example:
{    
"connection.timeout": 1000
}

2. Secret values, Must not be stored in Git for security reasons. These variables we create in cloud manager. For example:

{ 
"password": "$[secret:password]"
}

3. Environment-specific values, which are values that vary between Development environments, and thus cannot be accurately targeted by run mode (since there is a single dev runmode in Adobe Experience Manager as a Cloud Service). For example:

{  
"username": "$[env:username]"
}

A single OSGi configuration file can use any combination of these configuration value types in conjunction. For example:

{ 
"connection.timeout": 1000,
"password": "$[secret:password]",
"username": "$[env:username]"
}

Note: As of today, up to 200 variables per environment can be declared.

Secret and environment value we can set with the help of cloud manager following below steps.

Set Secret and Environment Specific Variables

Using Cloud Manager we can set secret and environment specific variables and its default values.

Cloud Manager allow us to manage all the AEM as a cloud service environments and the same time provide us the capability to deploy code. It is a pre-requisite to have an access of cloud manager to deploy code on the environments. We can access Cloud Manager using link and credentials as our personal Adobe ID.

Follow below steps once we are good with pre-requisites.

  1. access Cloud Manager using link and select below highlighted program.

2. Select below highlighted option View Details of specific environment.

3. Go to Configuration tab and click on Add Configuration button.

4. Enter password and its value as an OSGI configuration. Also, please select service applied property as All to have these configuration available on both author and publish instance for all edit, preview and publish mode.

5. Please select type of configuration as variable or secret.

Please select Secret as part of Type dropdown as show below.

Please select Secret as an option for password as it is highly secure and to be secret.

6. Click on Save button to save the changes.

7. Below are the saved OSGI configurations.

8. Below is the actual mapping of OSIG configuration in code and Cloud Manager. These properties will get automatically get pick after successful deployment.

Note: We can also add environment specific and secret configuration values with the help of cloud manager API. Follow this link to have YAML file which provides over all information regarding the API such as url, parameter, method type, headers, response, content type, etc.

We can also use aio (Adobe I/O) to create or add environment specific and secret configuration using aio command line as shown below. Follow this link to read more about aio.

Variable Naming Convention

Environment specific and secret configuration variables names must follow the following rules:

  • Minimum length: 2
  • Maximum length: 100
  • Must match regex: [a-zA-Z_][a-zA-Z_0–9]*

Environment specific and secret configuration Values for the variables must not exceed 2048 characters.

Note:

There are rules related to the use of certain prefixes for variable names:

  • Variable names prefixed with INTERNAL_, ADOBE_, or CONST_ are reserved by Adobe. Any customer-set variables that start with these prefixes are ignored.
  • Customers must not reference variables prefixed with INTERNAL_ or ADOBE_ either.
  • Environment variables with the prefix AEM_ are defined by the product as Public API to be used and set by customers.
    While customers can use and set environment variables starting with the prefix AEM_ they should not define their own variables with this prefix.

Default Values

The following applies to both environment specific and secret configuration values.

If no per-environment value is set, at runtime the placeholder is not replaced and is left in place since no interpolation happened. To avoid this, a default value can be provided as part of the placeholder with the following syntax:

$[env:ENV_VAR_NAME;default=<value>]

OSGI Configuration Implementation

Follow below steps to create OSGI configuration as part of AEM as a Cloud Service:

  1. Create GenericConfig interface which will help us to read the properties.
package com.practice.core.services;

public interface GenericConfig {

/**
* Gets the property.
* @param propertyName the property name
* @return the property
*/
public String getProperty(String propertyName);

}

2. Implement GenericConfig interface to read the property.

package com.practice.core.services.impl;

import com.practice.core.services.GenericConfig;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.Designate;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;

/**
* The Class GenericConfigImpl.
*/
@Component(service = GenericConfig.class, immediate = true, enabled = true, property = {
Constants.SERVICE_DESCRIPTION + "=Generic Config Properties configured per environment",
Constants.SERVICE_VENDOR + "=Practice" })
@Designate(ocd = GenericConfigImpl.Config.class)
public class GenericConfigImpl implements GenericConfig {

@ObjectClassDefinition(name = "Generic Config Properties",
description = "Generic Config Properties configured per environment")
public static @interface Config {

@AttributeDefinition(name = "process.label",
description = "process.label.description",
defaultValue = "GenericConfigImpl")
String process_label();
}

/** The service reference. */
@SuppressWarnings("rawtypes")
ServiceReference serviceReference = null;


/**
* Called when the Scheduler is activated/updated.
*
* @param componentContext ComponentContext
* @throws Exception Exception
*/
@Activate
protected final void activate(final ComponentContext componentContext, final GenericConfigImpl.Config config) {
config.process_label();
serviceReference = componentContext.getBundleContext().getServiceReference(GenericConfig.class.getName());
}

/*
* (non-Javadoc)
*
* @see com.practice.core.services.GenericConfig#getProperty(java.lang.String)
*/
public String getProperty(String propertyName) {
Object val = serviceReference.getProperty(propertyName);
return val == null ? "" : (String) val;
}

}

3. Create a util class to read GenericConfig.java OSGI configuration with the help of BundleContext.

package com.practice.core.utils;

import com.pratice.core.services.GenericConfig;
import org.apache.commons.lang3.StringUtils;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


@SuppressWarnings("squid:S2068")
public final class PracticeUtil {

/** The Constant LOGGER. */
private static final Logger LOGGER = LoggerFactory.getLogger(PracticeUtil.class);

private static final String SERVERSIDEAPI_USERNAME = "serverApiUsername";
private static final String SERVERSIDEAPI_PASSWORD = "serverApiPassword";

private PracticeUtil() {

}

/**
* Gets the GenericConfig properties from run modes
*
* @return GenericConfig
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public static GenericConfig getGenericConfig() {
LOGGER.debug("Entered into getConfig method");

BundleContext bundleContext =
FrameworkUtil.getBundle(PracticeUtil.class).getBundleContext();
ServiceReference serviceReference =
bundleContext.getServiceReference(PracticeUtil.class.getName());
GenericConfig genericConfig =
(GenericConfig) bundleContext.getService(serviceReference);

LOGGER.debug("Exited from getConfig method");
return genericConfig;
}

/**
* Gets the Server side API Username
*
* @return serverApiUsername
*/
public static String getServerApiUsername() {
LOGGER.debug("Entered into getServerApiUsername method");

String serverApiUsername = StringUtils.EMPTY;
if (getGenericConfig().getProperty(SERVERSIDEAPI_USERNAME) != null) {
// gets the username for Server side API
serverApiUsername = getGenericConfig().getProperty(SERVERSIDEAPI_USERNAME);
}
LOGGER.debug("Exited from getServerApiUsername method");

return serverApiUsername;
}

/**
* Gets the Server side API Password
*
* @return serverApiPassword
*/
public static String getServerApiPassword() {
LOGGER.debug("Entered into getServerApiPassword method");

String serverApiPassword = StringUtils.EMPTY;
if (getGenericConfig().getProperty(SERVERSIDEAPI_PASSWORD) != null) {
// gets the pwd for server side API
serverApiPassword = getGenericConfig().getProperty(SERVERSIDEAPI_PASSWORD);
}
LOGGER.debug("Exited from getServerApiPassword method");

return serverApiPassword;
}
}

4. Create a servlet to access OSIG configuration with the help of PracticeUtil.java class.

package com.javadoubts.core.servlets;

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.practice.core.utils.PracticeUtil;

import javax.servlet.Servlet;
import java.io.IOException;

@Component(
service=Servlet.class,
property={
Constants.SERVICE_DESCRIPTION + "=Custom Servlet",
"sling.servlet.methods=" + HttpConstants.METHOD_GET,
"sling.servlet.paths=" + "/bin/practice/testconfig"
}
)
public class SlingServletByPath extends SlingAllMethodsServlet {

private static final long serialVersionUID = 1L;

private final Logger logger = LoggerFactory.getLogger(getClass());

@Override
protected void doGet(final SlingHttpServletRequest req,
final SlingHttpServletResponse resp) throws IOException {
logger.error(">>>>>>>>> Entering Servlet >>>>>>>>>");

resp.setStatus(SlingHttpServletResponse.SC_OK);
resp.setContentType("application/json;charset=UTF-8");

resp.getWriter().print("{\"username\" : \""+ PracticeUtil.getServerApiUsername() +
"\", \"password\" : \""+ PracticeUtil.getServerApiPassword() + "\"}");

logger.error(">>>>>>>>> Exiting Servlet >>>>>>>>>");
}
}

5. Create below OSGI configuration in local having default value as part of com.practice.core.services.impl.GenericConfigImpl.cfg.json

OSGI configuration will require to have default values as we can not replicate ACM environment and secret variables in local as shown below:

{
"serverApiUsername": "$[env:server_username;default=admin]",
"serverApiPassword": "$[secret:server_password;default=admin123]"
}

6. Hitting below servlet in browser will print username and password http://localhost:4502/bin/practice/testconfig

Imran Khan, Adobe Community Advisor, AEM certified developer and Java Geek, is an experienced AEM developer with over 11 years of expertise in designing and implementing robust web applications. He leverages Adobe Experience Manager, Analytics, and Target to create dynamic digital experiences. Imran possesses extensive expertise in J2EE, Sightly, Struts 2.0, Spring, Hibernate, JPA, React, HTML, jQuery, and JavaScript.

0