Experience Sitecore ! | All posts by admin

Experience Sitecore !

More than 200 articles about the best DXP by Martin Miles

Keeping your own XM Cloud repository in sync with official XM Cloud starter kit template

XM Cloud is a live evolving platform – the development team releases new base images almost on a weekly basis, and new features are coming to the product regularly, which gets reflected in the underlying dependencies, as well as public starter kit templates such as XM Cloud Foundation Head Starter Kit.

At the same time XM Cloud professionals and enthusiasts and of course – the partners, are building their own starter kits based on publicly available templates provided by Sitecore. I alone have made more than a couple dozen personal improvements over the base starter kit that I am using almost on a daily basis. More to say, here at Perficient I am involved in building our brilliant JumpStart solution that comes as the essence of the company’s collective experience, with our best XM Cloud and Headless developers bringing and documenting their expertise as a part of the solution. The question comes to how to stay in sync with ever-coming changes and what would the best strategy for it?

One such strategy proposed was using a local copy of Foundation Head with semi-automating syncs using software called WinMerge. Despite finding this approach interesting and worth consideration, it does not fit the goals of Perficient XM Cloud JumpStart and is more suitable for smaller or personal repos. The fork-based solution is what seems to be the right path for JumpStart, retaining the ability to pull the latest features from the public template and merge them into its own private starter kit with minimal effort. And of course – being able to pull request back into a public repository, since we’re acting in the open-source community.

The problem that arises here is – Foundation Head is a public template repository on GitHub, and GitHub forking is done in such a manner that you can only fork public repositories into other public repos. We need a private repo with the ability to centrally control contributors’ access with SSO, GitHub Enterprise offers all that, but forking needs to get resolved first.

The Walkthrough

So here’s the walkthrough I keep in mind, simplified: I need to create a new private repo, clone the original repo locally, set up an additional remote, a new private would be an origin, and so on.  Below are the steps in detail.

First of all, we need to create a private repository, in my case, it will be called JumpStart, as we normally do with GitHub:

Create Repo

Next, let’s git clone the public repository, but with bare flag option:

1
git clone --bare https://github.com/sitecorelabs/xmcloud-foundation-head.git

bare repository is a special type of repository that does not have a working directory. It only contains the Git data (branches, tags, commits, etc.) without the actual project files. Bare repositories are typically used for server-side purposes, such as serving as a central repository for collaboration or as a backup/mirror of a repository. It creates a new bare repository in the current directory, cloning all branches and tags from the source repository.

1
2
cd xmcloud-foundation-head.git
git push --mirror https://github.com/PCPerficient/JumpStart.git

This command is used to push all branches and tags from your local repository to a remote repository in a way that mirrors the source repository. In the context of creating and maintaining a mirror or backup, you would typically use this command to push changes from your local bare repository (created with git clone --bare) to another remote repository.

1.clone And Mirror

So far so good. After mirroring, let’s clone the private repo so that we can work on it, as normal:

1
2
3
4
5
git clone https://github.com/PCPerficient/JumpStart
cd JumpStart
# do some changes as a part of normal workflow
git commit
git push origin master

Syncing Updated From Public Repository

Now, the interesting part: pulling new latest from the public template repo:

1
2
3
4
cd JumpStart
git remote add public https://github.com/sitecorelabs/xmcloud-foundation-head.git
git pull public master # this line creates a merge commit
git push origin master

2.pulling New From Public Repo

Awesome, your private repo now has the latest code from the public repo plus your changes.

Pull Request Back to the Open-Source

Finally, create a pull request from our private repository back to origin public repository. Assume, you’ve done some meaningful work in your clone private repository which you want to contribute back to the open-source community. So by that time you might have a feature branch in order to make a pull request into a master/main branch of your private repo

Unfortunately, you also won’t be able to do that with GitHub UI beyond the first step: with the GitHub UI of the public repository, create a fork (using “Fork” button at the top right of the public repository). Once done, our account (PCPerficient is this example) will have a public fork of the original repository (xmcloud-foundation-head). Then:

1
2
3
4
5
6
git clone https://github.com/PCPerficient/xmcloud-foundation-head.git
cd xmcloud-foundation-head
git remote add JumpStart https://github.com/PCPerficient/JumpStart.git
git checkout -b the_branch_you_want_to_pull_request
git pull private_repo_yourname master # you need to pull first prior making any pushes
git push origin the_branch_you_want_to_pull_request

The original public repository will get a pull request from a public fork at your GitHub account and will be able to review that and accept it.

Building Traefik Images with ltsc2022 for your Sitecore Deployments

Recently you can benefit from Sitecore providing ltsc2022 images for your XM/XP solutions which I previously covered in a seperate article. However, looking at your cluster you may see not all the images are ltsc2022 compatible – there is a 1809-based Traefik image, which is coming separately outside of the Sitecore docker registry.

Now, it’s a good time to get rid of that only left 1809-based Traefik image.

traefik - Official Image

The bad news is that there’s no ltsc2022 image for Traefik for us to use, the good news is that original Dockerfile is available, so I can rewrite it to consume ltsc2022 images. In addition, I took the latest (by the time) version of it which is 2.9.8, while the officially supported is 2.2.0, so it would make sense to parametrize the version as well, taking its settings from .env file settings.

I created a new docker\build\traefik folder and ended up with the following Dockerfile within there:

ARG IMAGE_OS
FROM mcr.microsoft.com/windows/servercore:${IMAGE_OS}
ARG VERSION

SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]

RUN Invoke-WebRequest \
        -Uri "https://github.com/traefik/traefik/releases/download/$env:VERSION/traefik_${env:VERSION}_windows_amd64.zip" \
        -OutFile "/traefik.zip"; \
    Expand-Archive -Path "/traefik.zip" -DestinationPath "/" -Force; \
    Remove-Item "/traefik.zip" -Force
EXPOSE 80
ENTRYPOINT ["/traefik"]

# Metadata
LABEL org.opencontainers.image.vendor="Traefik Labs" \
org.opencontainers.image.url="https://traefik.io" \
org.opencontainers.image.source="https://github.com/traefik/traefik" \
org.opencontainers.image.title="Traefik" \
org.opencontainers.image.description="A modern reverse-proxy" \
org.opencontainers.image.version=$env:VERSION \
org.opencontainers.image.documentation="https://docs.traefik.io"
        

Because of that I also had to update the related docker-compose section of docker-compose.override.yml file:

traefik:
    isolation: ${ISOLATION}
    image: ${REGISTRY}traefik:${TRAEFIK_VERSION}-servercore-${EXTERNAL_IMAGE_TAG_SUFFIX}
    build:
    context: ../../docker/build/traefik
    args:
        IMAGE_OS: ${EXTERNAL_IMAGE_TAG_SUFFIX}
        VERSION: ${TRAEFIK_VERSION}
    volumes:
    - ../../docker/traefik:C:/etc/traefik
    depends_on:
    - rendering

What I want to pay attention to here - I am now using ${ISOLATION} as the rest of the containers are using instead of dedicated TRAEFIK_ISOLATION which can now be removed from .env.

Another thing is that I am passing fully parametrized image names:

image: ${REGISTRY}traefik:${TRAEFIK_VERSION}-servercore-${EXTERNAL_IMAGE_TAG_SUFFIX}

I intentionally do not prefix it with ${COMPOSE_PROJECT_NAME} so that this image becomes reusable between several solutions on the same machine, which saves some disk drive space.

Last step would be adding .env file parameter TRAEFIK_VERSION=v2.9.8 and removing TRAEFIK_IMAGE parameter which is no longer needed. Good to go!

Traefik in action

Verdict

I tested all of the important features of the platform, including Experience Editor and it all works, and what is especially important – works impressively fast with the Process isolation mode. And since all the containers are built with ltsc2022 and run in Process isolation, one doesn’t need Hyper-V at all!

As for me, I ended up having a nice and powerful Windows 11 laptop suitable for modern Sitecore headless operations with a minimum overhead due to the Process isoolation.

Enjoy faster development!

XM Cloud: a modern way to content management and import with Authoring GraphQL API

When it comes to content management, how would you deal with automating it?

You’ve probably thought of Data Exchange Framework for setting up content import from external sources on a regular basis, or Sitecore PowerShell Extensions as the universal Swiss Army knife that allows doing everything.

Sitecore XM Cloud is a modern SaaS solution and therefore offers you one more way of managing and importing content via GraphQL mutations. This is also an option for the latest 10.3 XM/XP platforms bringing them a step closer to the composable world of today.

There is a video walkthrough of the wholee exercise at the bottom of this post.

Please welcome: Authoring and Management API!

The documentation prompts a broad and awe-inspiring list of things you can do with Authoring API against your instance: create and delete items, templates, and media. It also empowers you to do some operations around site context and perform a content search on your CM instance.

Management API in addition gives you control over operations using queries and mutations for the following GraphQL types:

  • Archiving
  • Database
  • Indexing
  • Job
  • Language
  • Publishing
  • Security
  • Workflow
  • Rules

With that in mind, you can create and structure your content, reindex, and publish it to Experience Edge entirely using this API. So let’s take a look at how it works!

Uploading a picture to Media Library using GraphQL Authoring API

First of all, it is disabled by default, so we need to switch by setting Sitecore_GraphQL_ExposePlayground environmental variable to true. Since these variables expand at build time you also need to re-deploy the environments

Enable Api

Once deployment is complete, you can start playing with it. Security in a composable world typically works with OAuth, Authoring and Management API is not an exclusion here. In order to obtain an access token, you need to authorize it first with your client ID and client secret which you set up with XM Cloud Deploy app:

Token

There are different ways of authorization (for example, using CLI dotnet sitecore cloud login command), but since the need to fully automate the routine, I will be using /oauth/token endpoint. Also, it is worth mentioning that after getting initially already authorized with CLI, your client ID / secret pair is stored at .sitecore\user.json file so let’s take it from there. Here’s the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$userJson = "$PSScriptRoot/../../.sitecore/user.json"
 
if(-not(Test-Path$userJson)){
    Write-Error"The specified file '$userJson' does not exist."
    return
}
 
$userJson = Get-Content$userJson | ConvertFrom-Json
$clientId = $userJson.endpoints.xmCloud.clientId
$clientSecret = $userJson.endpoints.xmCloud.clientSecret
$authorityUrl = $userJson.endpoints.xmCloud.authority
$audience = $userJson.endpoints.xmCloud.audience
$grantType = "client_credentials"
 
$body = @{
    client_id = $clientId
    client_secret = $clientSecret
    audience = $audience
    grant_type = $grantType
}
 
$response = Invoke-RestMethod -Uri "${authorityUrl}oauth/token" -Method Post -ContentType "application/x-www-form-urlencoded" -Body $body
return $response.access_token

Now we got the access token and it should be passed as a header with every single request to GraphQL API:

“Authorization” = “Bearer <access_token>”

Next, let’s make a mutation query that returns us a pre-signed upload URL from the passing API endpoint and a target Sitecore path that you want to upload your media to. Here’s the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[CmdletBinding()]
Param(
    [Parameter(Mandatory=$true, HelpMessage="The URL of the endpoint where the file will be uploaded.")]
    [string]$EndpointUrl,
    [Parameter(Mandatory=$true, HelpMessage="The JWT token to use for authentication.")]
    [string]$JWT,
    [Parameter(Mandatory=$true, HelpMessage="The path of the file to be uploaded.")]
    [string]$UploadPath
)
 
$query = @"
mutation
{
  uploadMedia(input: { itemPath: "$UploadPath" }) {
    presignedUploadUrl
  }
}
"@
 
$body = @{ query = $query} | ConvertTo-Json
$headers = @{
    "Content-Type" = "application/json"
    "Authorization" = "Bearer $JWT"
}
 
# Invoke the GraphQL endpoint using Invoke-RestMethod and pass in the query and headers
$response = Invoke-RestMethod -Method POST -Uri $EndpointUrl -Headers $headers -Body $body
$result = $response.data.uploadMedia
return $result.presignedUploadUrl

Now that we have the pre-signed upload URL, we can perform media upload passing the local file to process:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[CmdletBinding()]
Param(
    [Parameter(Mandatory=$true, HelpMessage="The URL to upload the file to.")]
    [string]$UploadUrl,
    [Parameter(Mandatory=$true, HelpMessage="The JWT token to use for authentication.")]
    [string]$JWT,
    [Parameter(Mandatory=$true, HelpMessage="The path to the file to be uploaded.")]
    [string]$FilePath
)
 
if(-not(Test-Path$FilePath)){
    Write-Error"The specified file '$FilePath' does not exist."
    return
}
 
$result = & curl.exe --request POST $UploadUrl --header "Authorization: Bearer $JWT" --form =@"$FilePath" -s
$result = $result | ConvertFrom-Json
return $result

This script will return the details of a newly uploaded media item, such as:

  • item name
  • item full path
  • item ID

I combined all the above cmdlets into a single Demo-UploadPicture.ps1 script that cares about passing all the parameters and performs the upload operation:

Powershell

The upload immediately results in Media Library at the requested path:

Result

Pros:

  • a modern platform agnostic approach
  • works nicely with webhooks
  • allows automating pretty much everything
  • excellent management options making DevOps easier

Cons:

  • cumbersome token operations
  • doesn’t allow batching, therefore takes a request per each operation

Verdict

It is great to have a variety of different tools in your belt rather than having a single hammer in a hand with everything around turning into nails. Hope this new tool brings your automation skills to a new level!