Friday, October 15, 2021

How to deliver headless content through GraphQL API and Content Fragments? | AEM(Adobe Experience Manager)

How to deliver headless content through GraphQL API and Content Fragments? | AEM(Adobe Experience Manager)

Adobe Experience Manager (AEM) Content Fragments allow you to design, create, curate and publish page-independent content. They allow you to prepare content ready for use in multiple locations/over multiple channels.

GraphQL is a query language and server-side runtime for application programming interfaces (APIs) that prioritizes giving clients exactly the data they request and no more. GraphQL is designed to make APIs fast, flexible, and developer-friendly.

AEM as a Cloud Service and AEM 6.5.10.0+ version supports GraphQL API to expose the Content Fragment to enable the headless content experience. AEM as a Cloud Service and AEM 6.5.10.0 versions enable the GraphQL runtime platform to expose the Content Fragments through GraphQL API.

Using the GraphQL API in AEM enables the efficient delivery of Content Fragments to JavaScript clients in headless CMS implementations:
  • Avoiding iterative API requests as with REST,
  • Ensuring that delivery is limited to the specific requirements,
  • Allowing for bulk delivery of exactly what is needed for rendering as the response to a single API query.

AEM GraphQL API


GraphiQL Interface:


AEM GraphQL implementation provides a standard GraphQL interface to directly input, and test queries. The interface should be installed separately, the interface can be downloaded from Adobe software-distribution - https://experience.adobe.com/#/downloads/content/software-distribution/en/aemcloud.html?package=/content/software-distribution/en/details.html/content/dam/aemcloud/public/aem-graphql/graphiql-0.0.6.zip, install the latest version of the interface

AEM GraphQL API

GraphiQL interface is bound to the global endpoint (and does not work with other endpoints for specific Sites configurations) - the global endpoint includes all the site-specific configurations(the models created under site-specific conf folders).


AEM GraphQL API


You may receive the below error while executing the API's in the AEM Author

{
  "message": "Failed to execute 'text' on 'Response': body stream already read",
  "stack": "TypeError: Failed to execute 'text' on 'Response': body stream already read\n    at http://localhost:4502/content/graphiql.html:81:33\n    at async Object.onRun (https://unpkg.com/graphiql/graphiql.min.js:1:533801)"
}

AEM GraphQL API

The actual root cause was the CSRF filter blocking the requests in AEM Author, the path white listing looks to be not enabled while upgrading from 6.5.8.0 to 6.5.10.0(but it worked for me while upgrading from 6.5.0.0 to 6.5.10.0), the whitelisting is already enabled in AEM as a Cloud Service, ensure the blow paths are whitelisted in the CSRF filter.

AEM graphql API

Configuration Browsers - Enable Content Fragment Model/GraphQL Persistent Queries:


Enable the Content Fragment Model/GraphQL Persistent Queries through configuration browser for the global sites configurations and the site-specific configurations.

Tools -- General -- Configuration Browser -- Select Global folder -- Properties, ensure Content Fragment Models and GraphQL Persistent Queries are selected

AEM GraphQL API


Enable Content Fragment Model(Model your content):

Model your content structure using AEM Content Fragment Models and Content Fragments.

Let us now enable the content fragment model that should be used to generate Content Fragments that will be exposed to the external system through GraphQL API for consuming the content.

Tools -- Assets -- Content Fragment Models, Select the global or Site-Specific folder(I am creating under the site-specific folder) and create a new Model. Ensure the model is enabled, GraphQL schema will be generated only for the models that are enabled

AEM GraphQL API

GraphQL tab displays the API details

AEM GraphQL API

Open the model and enable the required fields - additional data types e.g Content Reference, Fragment Reference, JSON Object and etc can be used if required while structuring the Content Fragment Model. The referenced Model(Fragment) data can also be fetched through the parent GraphQL API e.g. if Employee Model references Person Model, the associated Person data can be fetched through Employee GraphQL API.

AEM GraphQL API

The default Cache configurations can be modified if required through Content Fragment Model properties

AEM GraphQL API

GraphQL Endpoints:


AEM endpoint helps to access the GraphQL schema, also send GraphQL queries and receive the response.

Define the API endpoint by going to Tools -- Assets -- GraphQL

The global endpoint will be defined by default(enable if missing), define the site-specific endpoints if required 
(global endpoint expose all the models if required site-specific endpoints can be enabled but the whitelisting and other configurations should be enabled for site-specific endpoints)

AEM GraphQL API

AEM GraphQL API


The endpoint should be published to make it available to the publisher. Ensure the appropriate security permissions are set up while enabling the GraphQL API endpoints in publishers. 

GraphQL Schema:


GraphQL is a strongly typed API, which means that data must be clearly structured and organized by type.

The GraphQL specification provides a series of guidelines on how to create a robust API for interrogating data on a certain instance. To do this, a client needs to fetch the Schema, which contains all the types necessary for a query.

In AEM, the schema is generated based on the Content Fragment Model, the schema is flexible. This means that it is auto-generated each and every time a Content Fragment Model is created, updated, or deleted. The data schema caches are also refreshed when you update a Content Fragment Model.

The schema can be accessed through endpoint API - http://localhost:4502/content/cq:graphql/global/endpoint.GQLschema

Also, schema and schema docs can be fetched through the GraphQL interface(additional fields can be added if required)

{
  __schema {
    types {
      name
      description
    }
  }
}

AEM GraphQL API


Create Content Fragments:


Now create the required Content Fragments under the appropriate asset folder(e.g mysite) with the required data, select the Content Fragment Model created in the earlier step.

Ensure the Content Fragment Models are enabled for the asset folders(e.g mysite)

AEM GraphQL API


AEM GraphQL API

Enter the required data and save it, the model should be published to enable the access with publish server.

AEM GraphQL API


Content Fragments now enables preview for GrapQL data, the preview will work only if the site-specific endpoint is registered.

AEM GraphQL API

Register the site-specific endpoint to preview the content

AEM GraphQL API

Now you should be able to preview the data

AEM GraphQL API

You should be able to copy the direct GraphQL data URL - http://localhost:4502/content/dam/mysite/person1.cfm.gql.json ( this will expose the Content Fragment Data as JSON)

Fetching the Data:


Now the data can be queried based on the GraphQL API's defined in the schema - http://localhost:4502/content/cq:graphql/global/endpoint.GQLschema

"""
Get a single `PersonModel`, specified by its path and optional variation
"""
personByPath(_path: String!, variation: String): PersonModelResult! @fetcher(name : "sites/default", source : "personByPath")
"""

"""
Get multiple `PersonModel` objects
"""
personList(filter: PersonModelFilter, variation: String, _locale: String): PersonModelResults! @fetcher(name : "sites/default", source : "personList")
"""
Fetch the person details by path:

The personByPath API can be used to fetch content fragment specific data(the Content Fragment created based on Person Model) by specifying the content fragment path, modify the attributes(I am fetching firstName, lastName, and email) based on your needs

query{
  personByPath(_path:"/content/dam/mysite/person1"){
   item {      
      firstName
      lastName
      email
    }
  }
}

AEM GraphQL API


Fetch Person Lists:

The personList API can be used to fetch a list of Content Fragments created using the Person Model.

query{
  personList{
    items {
      _path
      firstName
      lastName
      email      
      
    }
  }
}

AEM GraphQL API


Filter based on expression:

The filters can be specified to fetch specific content fragments from the list of content fragments by specifying filter expressions.

query {
  personList(filter: {
    firstName: {
     _expressions: [
        {
          value: "Test1"
          _operator: EQUALS_NOT
        }
    ]
    }
  }) {
    items {
      firstName
      lastName
      email
    }
  }
}

AEM GraphQL API

For the Multiline Text fields, the below properties can be used to fectch the different format data

html
plaintext
markdown
json

query{
  personByPath(_path:"/content/dam/mysite/person1"){
   item {      
      firstName
      lastName
      email
      additionalDetails
     {
      html
      markdown
      plaintext
      json
     }
    }
  }
}

AEM GraphQL API


The metadata and variation details can also be fetched, also other expressions can be used to filter the data - refer to https://github.com/AdobeDocs/experience-manager-cloud-service.en/blob/master/help/assets/content-fragments/content-fragments-graphql-samples.md#content-fragment-structure-graphql for more query operations and fields.

The GraphQL variables and Directives can be used to filter the data, refer https://experienceleague.adobe.com/docs/experience-manager-cloud-service/assets/admin/graphql-api-content-fragments.html?lang=en for more details.

Persisted Queries (Caching):


Prepare query with a POST request, it can be executed with a GET request and the responses can be cached by HTTP caches or a CDN. The Persisted queries help to improve the overall performance and also reduce the load on AEM servers.

Create Persisted Query(PUT) - may be the easy option is to copy the curl command from the GraphQL interface and modify 

AEM GraphQL API

The persisted query can be created by sending the PUT request to http://localhost:4502/graphql/persist.json/<Site Name>/<Query Name>

I am using the basic authentication for the demo but the token-based authentication should be used for AEM as a Cloud Service.

curl -u admin:admin -X PUT 'http://localhost:4502/graphql/persist.json/mysite/person' \
  -H 'Content-Type: application/json' \
  --data-raw '{"query":"query{\n  personByPath(_path:\"/content/dam/mysite/person1\"){\n   item {      \n      firstName\n      lastName\n      email\n    }\n  }\n}","variables":null}'

The output will provide a short path that can be used to execute the query e.g /mysite/person

Execute Persisted Query - the persisted query can be executed by sending get request to 
http://localhost:4502/graphql/execute.json/<Query Shortname>

curl -u admin:admin -X GET http://localhost:4502/graphql/execute.json/mysite/person

The existing persisted queries can be modified by sending the POST request to http://localhost:4502/graphql/persist.json/<Site Name>/<Query Name>

curl -u admin:admin -X POST 'http://localhost:4502/graphql/persist.json/mysite/persion' \
  -H 'Content-Type: application/json' \
  --data-raw '{"query":"query{\n  personByPath(_path:\"/content/dam/mysite/person1\"){\n   item {      \n      firstName\n      lastName\n      email\n    }\n  }\n}","variables":null}'

The max cache header value can be specified while creating the persisted queries - this will help us to cache the query result for an extended time

curl -u admin:admin -X PUT 'http://localhost:4502/graphql/persist.json/mysite/persion' \
  -H 'Content-Type: application/json' \
  --data-raw '{"query":"query{\n  personByPath(_path:\"/content/dam/mysite/person1\"){\n   item {      \n      firstName\n      lastName\n      email\n    }\n  }\n}","variables":null,"cache-control":{"max-age":300}}'

The persisted  queries are stored under /conf/<Site Name>/settings/graphql/persistedQueries

AEM GraphQL API

The list of Persisted queries can be fetched through /graphql/list.json endpoint - curl -u admin:admin -X GET   http://localhost:4502/graphql/list.json

To delete the existing query, send the DELETE request to the /graphql/persist.json/<Short Path> -  curl -u admin:admin -X DELETE   http://localhost:4502/graphql/persist.json/mysite/test



Consume GraphQL API Data from External System:


The external system can retrieve the Content Fragment data by sending the POST request to /content/_cq_graphql/global/endpoint.json GraphQL API endpoint(copy the curl request from GraphQL interface UI)

I am using the basic authentication for the demo but the token-based authentication should be used for AEM as a Cloud Service(Let me explore and share this later). Every Content Fragment model can be accessed through a global endpoint but individual endpoints can be enabled if required.

  curl -u admin:admin 'http://localhost:4502/content/_cq_graphql/global/endpoint.json' \
  -H 'Content-Type: application/json' \
   --data-raw '{"query":"query{\n  personByPath(_path:\"/content/dam/mysite/person1\"){\n   item {      \n      firstName\n      lastName\n      email\n    }\n  }\n}","variables":null}'


The CORS and  Referrer filter should be configured to enable the access for external systems(for the demo I am showing the OSGI console but enable through code - AEM as a Cloud service won't provide access to OSGi console, ready only view is provided through Developer console - refer https://www.albinsblog.com/2021/10/developer-console-and-adobe-io-cli-for-cloud-manager-to-debug-aem-as-a-cloud-service.html for more details)

Enable the external domains as Allowed Origins and the below paths as Allowed Paths  through Adobe Granite Cross-Origin Resource Sharing Policy
/content/_cq_graphql/global/endpoint.json // everything should be accessible through global, individual endpoints can be enabled if required
 /graphql/execute.json/.* // endpoint to execute the persisted queries

AEM GraphQL API


The CORS Filter should be enabled while testing thorugh localhost SPA application, also ensure the Authorization token is whitelisted.

AEM Headless GraphQL CORS

Referrer filter must be configured to allow access from external hosts.

AEM GraphQL API

Now the external system will be able to fetch the Content Fragment data from AEM through GraphQL API queries.

The GraphQL API of AEM provides a powerful query language to expose data of Content Fragments to downstream applications to support headless content sharing with external systems. Content Fragment models define the data schema that is used by Content Fragments. Whenever a Content Fragment Model is created or updated, the schema is translated and added to the “graph” that makes up the GraphQL API.

The GraphiQL IDE allows you to quickly test and refine the queries and data returned. GraphiQL also provides easy access to the documentation, making it easy to learn and understand what methods are available.

The AEM GraphQL API currently not supporting some of the GraphQL API features such as Mutations, Subscriptions, and Paging/Sorting, the API is read-only and the Asset API should be used to perform the data management operations.

References:




Tuesday, October 12, 2021

Developer Console and Adobe I/O CLI for Cloud Manager to Debug AEM as a Cloud Service (AEMaaCS)

Developer Console and Adobe I/O CLI for Cloud Manager to Debug AEM as a Cloud Service (AEMaaCS)

AEM as a Cloud Service provides a Developer Console for each environment that exposes various details of the running AEM service that are helpful in debugging. Each AEM as a Cloud Service environment has its own Developer Console. 

Adobe I/O CLI for Cloud Manager provides commands to download/tail the logs from AEM as a Cloud Service environment.

Some of the below Adobe documents provides the required details on this topic, thought of sharing my learning for reference

Debugging AEM as a Cloud Service | Adobe Experience Manager

The Developer console can be accessed through Cloud Manager or Adobe I/O CLI for Cloud Manager

Log in to CM, select the appropriate Program(Select the Program enabled for AEM as a Cloud - the AMS customers participating in AEM as a Cloud will have two different programs)

AEM as a Cloud Service

Click on the three dots for the specific environment, and select “Developer Console”, the same can be accessed through the Environment tab. You will be able to see the Developer Console with the environment details.

AEM as a Cloud Service

AEM as a Cloud Service

Change the value in the pod dropdown list to view the different server details on the environment.

To access and use the Developer Consol, the developer must be a member of the Cloud Manager Product’s Developer - Cloud Service Product Profile. Also, the developer must be a member of the AEM Users or AEM Administrators Product Profile on AEM Author and/or Publish. The exception being for Sandbox Programs, where any user with access to the Cloud Manager Sandbox Program will have access to Developer Console.

The Developer Console provides similar information as the AEM SDK’s local quickstart’s OSGi Web console, with the marked difference that the Developer Console is read-only.

As illustrated above, available statuses information includes the state of bundles, components, OSGI configurations, oak indexes, OSGI services, and Sling jobs.

Also, the Query Performance tool can be accessed from the Queries tab - /libs/granite/operations/content/diagnosistools/queryPerformance.html.

Sandbox Program environments enter a hibernation mode if no activity is detected for a certain period of time. Hibernation is unique to Sandbox Program environments. Production program environments do not hibernate.

Hibernation is categorized as:

Automatic Sandbox Program environments are automatically hibernated after eight hours of inactivity, meaning that neither the author nor preview or publish services receive requests.

Manual: As a user, you may manually hibernate a Sandbox Program environment, although there is no requirement to do so since hibernation will occur automatically after a certain period (eight hours) of inactivity.

You will see the below screen if the Sandbox environment is Hibernated while accessing the instances

AEM as a Cloud Service

The Environment can be De-Hibernated through Developer Console

AEM as a Cloud Service


AEM as a Cloud Service

AEM as a Cloud Service

The Developer Console can be also accessed through Adobe I/O CLI for Cloud Manager

aio cloudmanager:open-developer-console <ENVIRONMENTID> --programId <PROGRAMID>

The Environment ID and Program ID can be retrieved by accessing the environment through Cloud Manager.

AEM as a Cloud Service

The Program and Environment ID's can be also retrieved through Adobe I/O CLI for Cloud Manager 

npm install -g @adobe/aio-cli
aio plugins:install @adobe/aio-cli-plugin-cloudmanager

aio auth:login
aio cloudmanager:org:select

aio cloudmanager:list-programs
aio config:set cloudmanager_programid <<Program ID>>
aio cloudmanager:list-environments

Executing the below command opens the Developer Console

aio cloudmanager:open-developer-console <ENVIRONMENTID> --programId <PROGRAMID>


The AEM server logs can also be accessed through Cloud Manager or Adobe I/O CLI for Cloud Manager

AEM as a Cloud Service

AEM as a Cloud Service


Logs can be tailed/downloaded through CLI commands

aio cloudmanager:environment:list-available-log-options <<Environment ID>> - this will list out all the avilable log services and names

Adobe I/O CLI for Cloud Manager


aio cloudmanager:environment:download-logs <<Environment ID>> <<Service>> <<Name>> [Number of days-default 1]

aio cloudmanager:environment:download-logs 22222 author aemerror 2

This will download the log file to the current folder - use the command parameters to change the location if required 

aio cloudmanager:environment:download-logs 22222 dispatcher aemdispatcher --outputDirectory=C:\test

The log can be tailed if required

aio cloudmanager:environment:tail-log <<Environment ID>> <<Service>> <<Name>>

aio cloudmanager:environment:tail-log 22222 author aemerror


The CRXDE Lite is only accessible on Local SDK instances and AEM as a Cloud Service Development environments but not available on Stage or Production environments.

Deployed OSGi configurations cannot be reviewed via CRXDE Lite. OSGi configurations are maintained in the AEM Project’s ui.apps code package at /apps/example/config.xxx, however upon deployment to AEM as a Cloud Service environments, the OSGi configurations resources are not persisted to the JCR, therefore not visible via CRXDE Lite. The OSGI configurations can be reviewed through the Developer console.

AEM as a Cloud Service

Oservervation while using the Adobe I/O CLI - I was getting 401 Unautorized exception while executing the CLI command, even aio auth:login not helped, the issue is resolved first by clearing the configurations(aio config:clear) then execute the aio auth:login and other commands


Friday, October 8, 2021

AEM(Adobe Experience Manager): Extend Experience Fragment model to support localized header/footer with custom site structure

AEM(Adobe Experience Manager): Extend Experience Fragment model to support localized header/footer with custom site structure

One of my earlier blogs talked about how to reuse the same editable templates with multiple sites, how to use the XF localization feature to enable different headers and footers for the websites build on the same template - there are multiple approaches to achieve this e.g. dedicated template for sites, enabling the header at page level rather in template level but we enabled the header in template level and used the localization feature to support the header variants.


The XF localization feature works well if the content(/content/mysite1/us/en) and experience fragment(/content/experience-fragments/mysite1/us/en) structures follows the same pattern.

Recently we had a use case to reuse the pages created with editable template under the legacy content path with localized headers/footers(independent websites)  - as  I said earlier this can be achieved in multiple ways e.g. create a dedicated template but we decided to extend the Experience Fragment localization logic with delegation pattern.

The global header XF is enabled under - /content/experience-fragments/mysite1/us/en/site/header/master and the content is under /content/mysite1/us/en - the header XF is embeded in the template structure, this supports localization by just enabling the copy of header XF and the content(e.g. XF - /content/experience-fragments/mysite1/fr/fr/site/header/master, Site - /content/mysite1/fr/fr). The need is to reuse the same content under(with same template) the legacy content path(e.g /content/mysite2/en-US), the default XF localization feature will not help here to enable the localized header for the pages under /content/mysite2/en-US as the the legacy content path structure is not matching with the global header embeded in the template.

The localization logic is enabled in the getLocalizedFragmentVariationPath() method of the XF core component model(com.adobe.cq.wcm.core.components.internal.models.v1.ExperienceFragmentImpl) , the Delegation pattern helps us to override the required methods(getLocalizedFragmentVariationPath()) from Core Component Models(ExperienceFragment) to enable the custom logic.

In a normal delegation process,  all the public methods from the original interface are added into the custom class and override or delegate the calls to the original class, this approach may create challenges while the existing methods are changed or new methods are introduced. The Lombok Java package helps here - inject all the public interface methods into the custom class and override only the required methods(this will help to avoid the challenges related to the version upgrade)

Let us now see how to use the Delegate pattern to override the Experience Fragment Model to support our use case.

The assumption is the legacy content is under three-level e.g. /content/mysite2/en-US - the logic can be changed based on your content hierarchy and the header will be placed under /content/experience-fragments/mysite2/en-US/site/header/master

Experience Fragment


As a first step, Create a custom XF Model in your core module - CustomExperienceFragmentImpl and associate it with the custom(proxy) XF component resource type.

@Model(adaptables = { Resource.class,
SlingHttpServletRequest.class }, adapters = ExperienceFragment.class, resourceType = CustomExperienceFragmentImpl.RESOURCE_TYPE, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)

@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION)
public class CustomExperienceFragmentImpl implements ExperienceFragment {

public static final String RESOURCE_TYPE = "mysite/components/experiencefragment"; // Specify the resourceType of the custom(Proxy) XF component
  //used to embed the headers and footers in to the template

Now create the delegate for the parent resource type model through @Self and @Via annotations

The @Delegate is the Lombok java library annotation to inject the delegated methods at compile-time, the exclusion can be defined- the methods that will be overridden by the custom class, in our case only the getLocalizedFragmentVariationPath method is overridden and the remaining methods are delegated.

@Self 
@Via(type = ResourceSuperType.class)
@Delegate(excludes = DelegationExclusion.class) // Define the the methods that should be exclude from the delegation
private ExperienceFragment delegate;


private interface DelegationExclusion { 
String getLocalizedFragmentVariationPath();
}

In the compile-time Lombok injects all the public methods into the custom class and delegate those calls to the parent class

public String getAppliedCssClasses() { return this.delegate.getAppliedCssClasses(); } public ComponentData getData() { return this.delegate.getData(); } etc.

Override getLocalizedFragmentVariationPath() with custom logic, the logic will check if there is any valid header/footer XF available in the defined location for the content path, if so the header/footer XF fragment path is returned. If there is no matching header the call will be delegated to the parent ExperienceFragment implementation. If required the call can be directly delegated to the parent class for the specific content path(the content path in which the new content structure is followed).

Add the latest Lombok library maven dependency to your core module

            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.20</version>
                <scope>provided</scope>
            </dependency>

Deploy the updated core module to the AEM server

Now, when the user accesses the content under /content/mysite2/en-US or /content/mysite2/fr-FR, the custom logic looks for the site-specific header XF under  /content/experience-fragments/mysite2/en-US/site/header/master or /content/experience-fragments/mysite2/fr-FR/US/site/header/master and returns the XF path if available, if not delegate the calls to the parent XF Model that applies the default logic(the XF path configured in the template will be displayed).

Refer to the below gist for the complete model(modify the model based on your use cases)



Additionally, if required enable page-level authoring fields(XF browsers) so that the authors can override the XF path configured in the template level for a specific websites.

Change the model logic to send back the overridden XF path

if (fragmentVariationPath.contains(XF_HEADER_VARIATION)) {

InheritanceValueMap ivm = new HierarchyNodeInheritanceValueMap(currentPage.getContentResource());
String headeroverrideXF=ivm.getInherited("headeroverride", String.class);
if(StringUtils.isNoneEmpty(headeroverrideXF) && resourceExists(headeroverrideXF))
{
return headeroverrideXF; 
}
}

The delegation pattern helps us to override the sling models to enable custom functionalities based on your use cases, using the Lombok java library helps us to simplify the generation of delegation methods - the Lombok library injects the delegation methods during compilation.

I want to highlight an issue faced while implementing this - delegate for all the public methods were not injected by Lombok library, the actual issue was an old version of Core Component dependency was added through aem-sdk-api but the latest Core Component version was enabled in the server(6.5), the issue got resolved after fixing the dependency issue(the XF was displaying blank data due to this issue).


Thursday, September 30, 2021

How to enable Basic Authentication for Non-Prod AEM websites? | Support Authenticated Performance Testing with Cloud Manager

How to enable Basic Authentication for Non-Prod AEM websites? | Support Authenticated Performance Testing with Cloud Manager

Enable Basic Authentication:

Most of the time we will have the use case to enable basic authentication for non-prod AEM websites to avoid non authenticated users accessing the content(another option is through IP whitelisting) also avoiding the non-prod contents getting indexed through google search(another approach to avoid the indexing is through Robots meta tag)

Easy to use generic user name/password for every website so that only the users know those credentials can access the password(if you need more security go with site-specific users or individual users)

In AEM the basic authentication can be enabled quickly through Dispatcher(Apache)

Create a common configuration file for authentication - /conf.d/htaccess/authentication.conf

   ## unsets authorization header when sending request to AEM

   RequestHeader unset Authorization

   AuthType Basic

   AuthBasicProvider file

   AuthUserFile /etc/httpd/conf.d/htaccess/credential.htpasswd 

   AuthName "Authentication Required"

   Require valid-user

Include this file into the individual Virtual Hosts

<Location />

    <If "'${ENV_TYPE}' =~ m#(dev|uat|stage)#">

Include /etc/httpd/conf.d/htaccess/authentication.conf

    </If>

</Location>

The ENV_TYPE can be set as an Environment variable e.g /etc/sysconfig/httpd (for AMS environment the required environment variables will be enabled by default)

ENV_TYPE='dev'

Create the credential file, execute the below command, you will be prompted to enter the password

htpasswd -c /etc/httpd/conf.d/htaccess/credential.htpasswd testuser

Execute the below command, to add additional users if required

htpasswd /etc/httpd/conf.d/htaccess/credential.htpasswd testuser1

Restart the Apache server, now basic authentication is enabled for the websites.

Authenticated Performance Testing with Cloud Manager:


Cloud Manager is a Cloud service that allows customers to build, test, and deploy AEM applications hosted by Adobe Managed Services. Refer to the below URL for more details

https://www.albinsblog.com/2020/11/beginners-tutorial-what-is-cloud-manager-in-aem.html

The Cloud Manager enables the Performance Testing on Stage environment after every successful deployment. 

The list of Stage domains enabled for performance testing can be configured - this configuration is not supported through UI but needs to be enabled through the backend(Adobe team should be helping here)

Also, the required performance KPI's can be configured through Cloud Manager UI

AEM Cloud Manager

Refer to the following URL for more details on performance testing - https://experienceleague.adobe.com/docs/experience-manager-cloud-manager/using/how-to-use/understand-your-test-results.html?lang=en#performance-testing

The performance testing will fail now due to the basic authentication enabled for the Stage websites, to overcome this the basic authentication credential should be configured in the Cloud Manager prod pipeline - the configuration cant be enabled through Cloud Manager UI but through Cloud Manager Plugin for the Adobe I/O CLI

This CLI supports two modes of authentication: Browser-based and Service Account. I am going to use browser-based authentication to enable the required configurations

As a prerequisite, ensure Node.js and NPM is installed in your machine, refer to the following URL for more details - https://www.npmjs.com/package/@adobe/aio-cli-plugin-cloudmanager

Identify the program ID and the production pipeline id by login to the Cloud Manager UI

AEM Cloud Manager

Execute the below commands in your machine(ensure you have the required permissions)

npm install -g @adobe/aio-cli

aio plugins:install @adobe/aio-cli-plugin-cloudmanager 

aio auth:login - login through browser with your Cloud Manager credentials

aio cloudmanager:org:select - select your organization where your program and the pipeline is configured(most of the case signle org)

aio config:set cloudmanager_programid PROGRAMID - Add your program ID

aio cloudmanager:set-pipeline-variables PIPELINEID--variable CM_PERF_TEST_BASIC_USERNAME testuser--secret CM_PERF_TEST_BASIC_PASSWORD pwdtestuser- Add your pipeline ID and the basic credentials

AEM Cloud Manager

Now the basic authentication is enabled and the basic authentication credential are set while performing the performance testing.



Tuesday, September 28, 2021

AEM as a Cloud Service - Sandbox Setup

 AEM as a Cloud Service - Sandbox Setup

"A Sandbox program is one of the two types of programs available in AEM Cloud Service, the other being a Production program.

A Sandbox is typically created to serve the purposes of training, running demos, enablement, or Proof of Concept (POC)s. They are not meant to carry live traffic."

Refer to the following URL to get the details on AEM as a Cloud Service Sandbox program - https://experienceleague.adobe.com/docs/experience-manager-cloud-service/implementing/using-cloud-manager/sandbox-programs/introduction-sandbox-programs.html?lang=en#

Enable Sandbox Program:


To start using the AEM as a Cloud Service Sandbox, the minimal required Adobe Enterprise Term License Agreement (ETLA) should be signed, you can reach out to Adobe Account/Support team for more details on this.

Once the ETLA is in place you should be able to add the sandbox program through Cloud Manager(one of your primary contacts will be added as an administrator, if you are already part of Cloud Manager through AMS then that will continue)

You will see an "Add Program" link after completing the ETLA and enabling the program from the backend -  https://experience.adobe.com/#/@xxxx/cloud-manager

Add a new program - Enter Program name and Program Type as Sandbox

AEM as a Cloud Service Sandbox

Now the new program will be listed - https://experience.adobe.com/#/@xxxx/cloud-manager

AEM as a Cloud Service Sandbox



Setup Sandbox:


Let us now set up the Sandbox environment, Click on the Sandbox program created in the previous step

Click on "Setup", this will take some time to configure the program

AEM as a Cloud Service Sandbox

Once the setup is completed you should be able to access the AEM servers and also deploy the code through the Cloud Manager pipeline.

AEM as a Cloud Service Sandbox


While accessing the Author server you will be taken to the below screen with the option to log in with Adobe ID

AEM as a Cloud Service Sandbox

The Administrator can enable the required access for the AEM users through Admin Console(https://adminconsole.adobe.com/)

AEM as a Cloud Service Sandbox


Select the Author Instance

AEM as Cloud Service Sandbox

Select the Profile the user should be added (AEM administrator or AEM user) - Create new profiles based on the need

AEM as a Cloud Service Sandbox


AEM as a Cloud Service Sandbox


Now you should be able to access the author server

AEM as a Cloud Service Sandbox

Sandbox program also enables Publish and Preview servers - You can view those details by going into the environment

AEM as a Cloud Service Sandbox



AEM as a Cloud Service Sandbox


Now you can push the changes to the git repository

AEM as a Cloud Service Sandbox


AEM as a Cloud Service Sandbox


By default the "main" branch is enabled for deployment, if required you can create the additional branch e.g dev, and enable the deployment through the new branch.

Now start the pipeline to deploy the changes

AEM as a Cloud Service Sandbox


AEM as a Cloud Service Sandbox


Even if you required the deployment can be initiated on repository change

AEM as a Cloud Service Sandbox

AEM as a Cloud Service Sandbox

The Sandbox setup is completed now,  enable the content and code based on your use cases.