Acorel
To Acorel.nl
Acorel background

Automating SAP Commerce Cloud deployments using Github Actions

Wilco Menge, 03 April 2024

In the past we have written about automated builds and deployments of SAP Commerce in SAP Commerce Cloud V2 (CCV2). Today I would like to show you how to achieve this with Github Actions, the CI/CD platform of Github. Our goal is to automate builds and deployments as much as possible to reduce errors and streamline the delivery of features.

 

What is Github Actions?

Github Actions is a CI/CD platform provided by Github, offering automation for building, testing, and deploying software. It allows you to automate activities related to building, testing and deploying your software. A lot of out of the box flows are available, for example, performing a sonar analysis or run a unit test suite or integration test suite. A comprehensive list can be found on the marketplace.

The marketplace however doesn’t contain any actions to build and deploy to CCV2, so that means we will have to do some configuration and scripting

Github Actions: Workflows, Jobs, Steps, Actions

Github actions consist of a number of components:

Diagram of an event triggering Runner 1 to run Job 1, which triggers Runner 2 to run Job 2. Each of the jobs is broken into multiple steps.

 

Finally, a number of workflows together can be packaged as an action and can be distributed separate from your application repository. We will not delve into this topic in this episode. Far more detailed information can be found in the documentation of Github actions

Implement Steps to Build and Deploy

The high level structure of a workflow is now clear: We will need a workflow that does a CCV2 Build followed by a CCV2 Deployment. but: we still need to figure out how to implement those steps. SAP supplies an API to perform most tasks that you can manually perform in the CCV2 Portal. There are a number of ways to tap into this functionality from Github Actions:

As building a bash script would introduce the least dependencies and overhead, for me this was the most interesting option to explore:

./github/scripts/build-sap-commerce.sh:

[bash]#!/bin/bash

branch=$1
timestamp=$(date +"%Y-%m-%d-%H-%M-%S")

create_build_output=$(curl -K "./.github/scripts/curl-config.txt" \
-X POST "https://portalapi.commerce.ondemand.com/v2/subscriptions/$SUBSCRIPTION_CODE/builds" \
--header "Authorization: Bearer $API_TOKEN" \
--data "{\"branch\":\"$1\",\"name\":\"$branch-$timestamp\"}")

# Check if the command succeeded
if [ $? -ne 0 ]; then
  echo "$create_build_output" | jq .
  exit 1
fi

code=$(echo $create_build_output | jq -r .code)
echo "Successfully created build:"
echo $create_build_output | jq .

# Share data between jobs
echo "build_code=$code" >> "$GITHUB_OUTPUT"

counter=0
status=UNKNOWN

while [[ $counter -lt 100 ]] && [[ "$status" == "UNKNOWN" || "$status" == "BUILDING" ]]; do
  let counter=counter+1 

  build_progress_output=$(curl -K "./.github/scripts/curl-config.txt" \
    --header "Authorization: Bearer $API_TOKEN" \
    "https://portalapi.commerce.ondemand.com/v2/subscriptions/$SUBSCRIPTION_CODE/builds/$code/progress")
    
  # Check if the command succeeded
  if [ $? -ne 0 ]; then
    echo "$build_progress_output" | jq .
    exit 1
  fi

  status=$(echo $build_progress_output | jq -r .buildStatus)
  percentage=$(echo $build_progress_output | jq -r .percentage)
  echo "$status $percentage%"

  if [[ "$status" != "UNKNOWN" && "$status" != "BUILDING" ]]; then
    echo "build has reached end state: $status"
    echo $build_progress_output | jq .

    if [[ "$status" == "SUCCESS" ]]; then
      exit 0
    fi

    if [[ "$status" == "FAILURE" ]]; then
      exit 1
    fi
  fi

  sleep 150

done[/bash]

The script above triggers a build by calling the /v2/subscriptions/$SUBSCRIPTION_CODE/builds endpoint. If creation of a build is succesful, the progress of the build is monitored by repeatedly performing requests to the /v2/subscriptions/$SUBSCRIPTION_CODE/builds/$code/progress endpoint until and end state (SUCCESS or FAILURE)   is reached. (In case something goes very wrong, we will also stop polling after 100 tries).

./github/scripts/deploy-sap-commerce.sh:

[bash]#!/bin/bash

create_deployment_output=$(curl -K "./scripts/curl-config.txt" \
  -X POST "https://portalapi.commerce.ondemand.com/v2/subscriptions/$SUBSCRIPTION_CODE/deployments" \
  --header "Authorization: Bearer $API_TOKEN" \
  --data "{\"buildCode\":\"$1\",\"databaseUpdateMode\":\"UPDATE\", \"environmentCode\": \"$2\", \"strategy\": \"ROLLING_UPDATE\"}")

# Check if the command succeeded
if [ $? -ne 0 ]; then
  echo "$create_deployment_output" | jq .
  exit 1
fi

code=$(echo $create_deployment_output | jq -r .code)
echo "Successfully created deployment:"
echo $create_deployment_output | jq .

counter=0
status=SCHEDULED

while [[ $counter -lt 100 ]] && [[ "$status" == "SCHEDULED" || "$status" == "DEPLOYING" ]]; do
  let counter=counter+1 

  deployment_progress_output=$(curl -K "./scripts/curl-config.txt" \
    --header "Authorization: Bearer $API_TOKEN" \
    "https://portalapi.commerce.ondemand.com/v2/subscriptions/$SUBSCRIPTION_CODE/deployments/$code/progress")
    
  # Check if the command succeeded
  if [ $? -ne 0 ]; then
    echo "$deployment_progress_output" | jq .
    exit 1
  fi

  status=$(echo $deployment_progress_output | jq -r .deploymentStatus)
  percentage=$(echo $deployment_progress_output | jq -r .percentage)
  echo "$status $percentage%"

  if [[ "$status" != "SCHEDULED" && "$status" != "DEPLOYING" ]]; then
    echo "build has reached end state: $status"
    echo $deployment_progress_output | jq .

    if [[ "$status" == "DEPLOYED" ]]; then
      exit 0
    fi

    if [[ "$status" == "FAIL" ]]; then
      exit 1
    fi
  fi

  sleep 150

done[/bash]

Very similar to the previous step: We create a deployment based on the build code of the previous step. If triggering of a deployment has been succesfull, the progress of the deployment is monitored by repeatedly performing requests to the /v2/subscriptions/$SUBSCRIPTION_CODE/deployments/$code/progress endpoint until and end state (DEPLOYED or FAIL) is reached. (In case something goes very wrong, we will also stop polling after 100 tries).

The workflow file will glue the steps together in one workflow, containing of 2 separate jobs:

.github/workflows/main.yml:

[yml]name: sap-commerce-ccv2-deployment
run-name: Deploy to SAP Commerce Cloud
on:
  workflow_dispatch:
  schedule:
    - cron: '0 22 * * *' # daily at 22:00
env:
  API_TOKEN: ${{ secrets.CCV2_API_TOKEN }}
  SUBSCRIPTION_CODE: ${{ vars.CCV2_SUBSCRIPTION_CODE }}  
jobs:
  build_commerce:
    runs-on: ubuntu-latest
    outputs:
      build_code: ${{ steps.build_commerce.outputs.build_code }}
    name: Build SAP Commerce
    steps:
      - uses: actions/checkout@v4
        with:
          sparse-checkout: .github
      - id: build_commerce
        run: ./.github/scripts/build-sap-commerce.sh develop
        shell: bash
  deploy_commerce:
    needs: build_commerce
    runs-on: ubuntu-latest
    name: Deploy SAP Commerce to CCV
    steps:
      - uses: actions/checkout@v4
        with:
          sparse-checkout: .github
      - env:
          BUILD_CODE: ${{needs.build_commerce.outputs.build_code}}
        run: ./.github/scripts/deploy-sap-commerce.sh $BUILD_CODE d1
        shell: bash[/yml]

The workflow consists of two jobs, build_commerce and deploy_commerce. Secrets and environment variable (CCV2_API_TOKEN and CCV2_API_TOKEN) defined in github ui and imported. For each job, first, the .github directory containing all scripts is fetched from the repository by the checkout action. Next the scripts are run in order: first build and (if successful) deploy. This workflow currently has two triggers: it can be started manually and will be run every night at 22:00

In github i’ve put the the complete example.

Further Possibilities

We have shown the basics of automating builds and deployment in CCV2 using Github actions. There are a lot of additional possibilities, such as:

If you have any questions, don’t hestitate to get in touch!

Receive our weekly blog by email?
Subscribe here: