Setup CI/CD Using GitHub Actions?

on

|

views

and

comments

What are GitHub Actions

GitHub Actions is a Continuous Integration & Deployment platform provided by GitHub that can be used to deploy your code from one environment to another environment. You can create workflows and jobs to trigger the pipeline and deployment.

How To Setup CI/CD Using GitHub Actions for Salesforce

  • Create .github a folder within the parent directory of your Git Repo
  • Create workflow folder within .github folder
  • Create github-actions-demo.yml file within workflow folder and use below sample YML code for the initial setup

image

name: GitHub Actions Demo
run-name: ${{ github.actor }} is testing out GitHub Actions 🚀
on: [push]
jobs:
  Explore-GitHub-Actions:
    runs-on: ubuntu-latest
    steps:
      - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
      - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
      - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
      - name: Check out repository code
        uses: actions/checkout@v3

      - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
      - run: echo "🖥️ The workflow is now ready to test your code on the runner."
      - name: List files in the repository
        run: |
          ls ${{ github.workspace }}
      - run: echo "🍏 This job's status is ${{ job.status }}."

The above pipeline is a simple GitHub Action pipeline

Pipeline Explanation

  • name : The name that will be displayed under the Actions Tab. This could be any meaningful word. Like Prod Pipeline, QA Pipeline, etc
  • run-name: The title that will be displayed when the GitHub Action will run
  • on : These are the events when you wanted to execute your pipeline. For Example, you only wanted to execute the pipeline when the pull request is merged then the event will be pull_request. To more all about the event Check the Official Document
  • Jobs : This is the place where we define our all Jobs that will be executed
  • runs-on: This is the name of the runner where you wanted to run your pipeline. I have used ubuntu-latest but you can use from the Available runners in GitHub Actions
  • steps: These are the steps that we define within our Jobs. For Example, installing the SFDX, Authenticating with Salesforce, Running Apex Test, Deployment, &, etc

Prepare your Salesforce Environment for Github Action CI/CD

Authentication

As we will be using the SFDX Commands to deploy the code using GitHub Action CI/CD tool so we need to authenticate using JWT. Please Follow Salesforce Document to generate the Certificate and Create the Connection Application inside Salesforce

  • Create an asset folder on the parent directory ( same level as the .github folder) of your git repo, we will use this in later steps image

Encrypt the server.key file using OpenSSL

Generate the Key & IV

Execute the below command within the folder where your server.key the file is located to generate the KEY & IV, once generated then please take a note and store it in some place from where you can easily get

openssl enc -aes-256-cbc -k GITHUBACTIONS -P -md sha1 -nosalt

Encrypt the server.key file & generate server.key.enc file

Execute the below command within the folder where your server.key the file is located to generate the encrypted file.

openssl enc -nosalt -aes-256-cbc -in server.key -out server.key.enc -base64 -K <KEY> -iv <IV>

Note: In the above command use your KEY & IV which you have generated in the previous step

Place server.key.enc file within the asset folder of your repo

image

Test #1

Now that we are done with the first step, let’s push this code to our GitHub and see the GitHub Action Running

image image image

Install SFDX CLI in the pipeline

Now, as we are done with the simple pipeline and we have also done with the steps for authentication with Salesforce! Let’s make modifications in our pipeline to add a job, here we will perform the steps related to Salesforce Deployment. The first step in this pipeline would be installing the SFDX and testing if the SFDX has been installed or not

In your pipeline yml file add the below code

build:
    runs-on: ubuntu-latest
    steps:
      # Checkout the Source code from the latest commit
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
          
      - name: Install NPM
        run: |
          npm install
      # Install the SFDX CLI using npm command
      - name: Install the SFDX CLI
        run: |
          npm install sfdx-cli --global
          sfdx force --help

If you are making the changes into GitHub directly, then commit the changes and see the magic. If you are making the changes in the local repo then you need to commit and push the changes to the remote branch.

Note: The indentation is very important in the pipeline. So you need to be very careful. You can use Online YML Validator to validate your YML file

Here is the yml file after making the above changes

name: GitHub Actions Demo
run-name: ${{ github.actor }} is testing out GitHub Actions 🚀
on: [push]
jobs:
  Explore-GitHub-Actions:
    runs-on: ubuntu-latest
    steps:
      - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
      - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
      - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
      - name: Check out repository code
        uses: actions/checkout@v3

      - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
      - run: echo "🖥️ The workflow is now ready to test your code on the runner."
      - name: List files in the repository
        run: |
          ls ${{ github.workspace }}
      - run: echo "🍏 This job's status is ${{ job.status }}."
      
  build:
    runs-on: ubuntu-latest
    steps:
      # Checkout the Source code from the latest commit
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
          
      - name: Install NPM
        run: |
          npm install
      # Install the SFDX CLI using npm command
      - name: Install the SFDX CLI
        run: |
          npm install sfdx-cli --global
          sfdx force --help

image image

Authenticate to Salesforce in Pipeline

In the above step, we have successfully installed the SFDX Pipeline the next step is to authenticate to Salesforce ORG so that we can perform the validation or deployment.

Decrypt the server.key.enc file

  • Remember we encrypted the server.key file at the initial steps and placed the outcome inside assets folder
  • Decrypt the server.key.enc file to get the server.key at runtime to make sure that we have the private key to establish the connection with Salesforce using the JWT method.
  • Add one more step within build job to decrypt the key. use below command
- name: Decrypt the server.key.enc file & store inside assets folder
        run: |
          openssl enc -nosalt -aes-256-cbc -d -in server.key.enc -out server.key -base64 -K <YOUR_KEY_VALUE> -iv <YOUR_IV_VALUE>

Note:- Use your key & iv value that you generated at the very first step

image

Authenticate to Salesforce using Pipeline

After we have decrypted the server.key in the previous and have got the key file that we need for authentication. Now, the time is to authenticate to Salesforce Username using JWT. Below is the command for authentication

sfdx force:auth:jwt:grant --clientid YOUR_CLIENT_ID --jwtkeyfile assets/server.key --username SALESFORCE_USERNAME --setdefaultdevhubusername -a HubOrg

Note

Replace YOUR_CLIENT_ID with your salesforce connected app consumer key Replace SALESFORCE_USERNAME with the salesforce deployment username

After making the changes, commit & push those changes to the remote branch and see the outcome! You must see the success message below image

Validate the code base to Salesforce Org

Congratulations 🎉, You have successfully authenticated to Salesforce Org. Now, the last step that is remaining is validating the code base to Salesforce Org. To validate/deploy the code base uses the below sfdx command

sfdx force:source:deploy -p force-app -c -u HubOrg

Where

  • -p path to source code
  • -c remove this if you want to deploy. -c is used to indicate that the code will be validated but not deployed
  • -u the target org username that is HubOrg as we have used HubOrg as an alias in the authentication command

image

If you want to do the direct deployment then remove -c from the above sfdx command

WoooooHoooooo 🎉 You have successfully developed a simple GitHub Action Pipeline that validates the code against salesforce org every time a push is happening in the repo.

Here is the complete YML file for your reference

name: GitHub Actions Demo
run-name: ${{ github.actor }} is testing out GitHub Actions 🚀
on: [push]
jobs:
  Explore-GitHub-Actions:
    runs-on: ubuntu-latest
    steps:
      - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
      - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
      - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
      - name: Check out repository code
        uses: actions/checkout@v3

      - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
      - run: echo "🖥️ The workflow is now ready to test your code on the runner."
      - name: List files in the repository
        run: |
          ls ${{ github.workspace }}
      - run: echo "🍏 This job's status is ${{ job.status }}."
      
  build:
    runs-on: ubuntu-latest
    steps:
      # Checkout the Source code from the latest commit
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
          
      - name: Install NPM
        run: |
          npm install
      # Install the SFDX CLI using npm command
      - name: Install the SFDX CLI
        run: |
          npm install sfdx-cli --global
          sfdx force --help
      - name: Decrypt the server.key.enc file & store inside assets folder
        run: |
          openssl enc -nosalt -aes-256-cbc -d -in assets/server.key.enc -out assets/server.key -base64 -K DECRYPTION_KEY -iv DECRYPTION_IV
          
      - name: Authenticate Salesforce ORG
        run: |
          sfdx force:auth:jwt:grant --clientid HUB_CONSUMER_KEY --jwtkeyfile assets/server.key --username HUB_USER_NAME --setdefaultdevhubusername -a HubOrg
      
      - name: Validate Source Code Against Salesforce ORG
        run: |
          sfdx force:source:deploy -p force-app -c -u HubOrg

Path Filtering in Github Action

In the current implementations anytime when the codebase is pushed to any branch then the pipeline is execting and because of this, we are validating the codebase even if there is not change in the code base. For example, if you change in the yml the file then also the pipeline is executing however this should not happen.

So, let’s add path filtering in Github Action

To include the path filters, we need to use paths in on events like push given is the example for the same

on: 
  push:
    paths:
      - 'force-app/**'

Commit and publish the changes, this time you will notice that no action is executed.

You can use the same concept for other folders as well and for the other events like pull_request

Add Environments in GitHub Actions

Adding the environment in Github Action is very important because whenever you are making the changes to the codebase and pushing the changes the validation runs against the org. What if you wanted to deploy the code to different environments like Integration, QA, Staging, SIT, &, etc and this will be an obvious use case? You must be deploying the code to a different environment.

Steps to create an Environment in GitHub Actions

  1. Open the Repo where you wanted to create an environment
  2. Click on the Setting tab to open the settings
  3. Locate the Environment item from the left side
  4. Click on New Environment to create a New Environment
  5. Give a name & click on Configure Environment

image

image

image

Congratulations 🎉 you have created the environment. If you wanted to create more environment then follow the same steps

Configures Secrets in Github Action Environments

Because we are using the values directly in the yml there are chances that some intruder can access the information and get unauthorized access to our Salesforce environment it is always best practice to create secrets and store all the sensitive information in secrets. For Example, username, key file, client id, login URL &, etc.

Also as the requirement is to deploy the code to various environments and the credentials and URL will be different for each environment.

  1. While you are on the environment page
  2. Click on the add secret button under Environment secrets section
  3. and add the following secrets for your environment
    • DECRYPTION_KEY is the value of the Key file to decrypt the server.key.inc file
    • DECRYPTION_IV is the value of the IV file to decrypt the server.key.inc file
    • ENCRYPTION_KEY_FILE is the location of the encrypted file that is assets/server.key.inc
    • JWT_KEY_FILE – the location to place the decrypted key file and the value should be asset/server.key
    • HUB_CONSUMER_KEY – the salesforce connected application id
    • HUB_USER_NAME – the salesforce username that needs to perform the validation/deployment. ( This should be the deployment username )
    • HUB_LOGIN_URL – the salesforce login URL depending upon whether it is the salesforce sandbox or production
  4. If you have multiple environments, then please add the secrets across all your environments

You can have the naming as per your need. If you use a different name then make sure to refer to those names in your pipeline If you have multiple environments, then make sure that the variable names are the same across all environments

image

Access Environment Secrets in your Pipeline

Add Environment in the build

Because we have set up the environment along with the secrets, first we need to tell our pipeline which environment the salesforce validation should be performed. The first step to modifying our job is build and add an environment keyword like below image

Refer to secrets in the steps

  • To access the secrets within the GitHub Action pipeline we need to first use $ followed by double opening flower brackets( {{ ) & double closing flower brackets( }} ). Example ${{ }}.
  • Within the flower, brackets use secrets the keyword followed by the period . statement followed by the name of the secrets. For Example – ${{ secrets.DECRYPTION_KEY }}
  • Replace all the hardcoding values with the secrets that you have just created.
  • Below is the modified code for the build job
build:
    runs-on: ubuntu-latest
    environment: developer
    steps:
      # Checkout the Source code from the latest commit
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
          
      - name: Install NPM
        run: |
          npm install
      # Install the SFDX CLI using npm command
      - name: Install the SFDX CLI
        run: |
          npm install sfdx-cli --global
          sfdx force --help
      - name: Decrypt the server.key.enc file & store inside assets folder
        run: |
          openssl enc -nosalt -aes-256-cbc -d -in ${{ secrets.ENCRYPTION_KEY_FILE }} -out ${{ secrets.JWT_KEY_FILE }} -base64 -K ${{ secrets.DECRYPTION_KEY }} -iv ${{ secrets.DECRYPTION_IV }}
          
      - name: Authenticate Salesforce ORG
        run: |
          sfdx force:auth:jwt:grant --clientid ${{ secrets.HUB_CONSUMER_KEY }} --jwtkeyfile  ${{ secrets.JWT_KEY_FILE }} --username ${{ secrets.HUB_USER_NAME }} --setdefaultdevhubusername -a HubOrg --instanceurl ${{ secrets.HUB_LOGIN_URL }} 
      
      - name: Validate Source Code Against Salesforce ORG
        run: |
          sfdx force:source:deploy -p force-app -c -u HubOrg

Commit and publish the changes. You will see that no Action is running because no changes have been made to the code base.

Test Environment based validation/deployment

To test the deployment or validation under the environment in my case developer make any changes in the code base and publish the changes. You will clearly see that it is deploying on the mentioned environment. image

If all the values are correct then you see the successful job below

image

Work with Pull Request in Github Action

It is ok to run the pipeline every time when there is a change in codebase pushed to a remote branch however when it comes to the higher environments like QA, staging, integration, or production then the pipeline should only execute when there is a pull_request raised and closed successfully.

Modify the developer pipeline

To take the most out of the Pull Request concept using Pipeline we need to make the following changes in our existing pipeline.

  • Add branches filter in the push event
  • Below is the code for your reference
on: 
  push:
    branches:
      - feature/*
    paths:
      - 'force-app/**'

Create a new pipeline

We have successfully created and tested the pipeline for the developer environment and branch. Now let’s create another pipeline that will execute when the pull request is raised to the master branch and is merged.

  • Create a new pipeline inside .github/workflow folder. You can give it any name, I will use production.yml
  • Copy and paste the same code as github-actions.yml
  • Change the environment to production under build job. Note:- This will require to create of a new environment with a name production and secrets setup
  • Change the name to Production Pipeline
  • change the run-name to ${{ github.actor }} is running pipeline on ${{ github.repository }}
  • for on use below code
on: 
  pull_request:
    types: [closed]
    branches:
      - master
      - main
    paths:
      - 'force-app/**'

Where

  • branches: This pipeline should only execute when there is a PR raised to master branch
  • types: Pipeline will execute only when the PR is closed. You can see all values from the Official Document
  • paths: only execute the pipeline when there is a change in force-app the folder that is a codebase

Create a New Branch

Because we have set up a production pipeline, to test the pipeline do follow the below steps

  • Create a branch out of maste or main branch and name it the developer
  • Make changes in the codebase in developer branch
  • Create a Pull request from developer branch to master or main branch
  • Merge the PR
  • Notice that the Pipeline on the Master branch has been executed

image image image image

If everything looks ok then you will see the success build like below image

Work with Delta Deployment

Delta deployment is very important these days to achieve selective deployment because in our current approach, we are deploying everything that is inside force-app no matter if we have changes in a single apex class it will deploy all the apex classes.

Because we are using sfdx deployment, we will be using an SFDX Plugin to generate the data at the run time. The plugin sfdx-git-delta is very helpful. This plugin is available for free and does not require any licensing.

Delta deployment will be helpful when we are deploying to the higher environment using Pull Request.

Create .sgdignore file

To make the Delta deployment using sfdx plugin, it is important to create the .sgdignore file and add the below content. We are creating this file because if you change the .yml file in the repo then the plugin will consider this file as workflow and a new entry will be added in package.xml which will fail the deployment.

The file must be in the topmost directory at the same level of force-app folder

# Github Actions
.github/
.github/workflow

Install the sfdx-git-delta plugin in Pipeline

To install the plugin, add the new step before decrypting the sever.key.inc file after the SFDX Installation step

- name: Install the sfdx-git-delta plugin
  run: |
    echo 'y' | sfdx plugins:install sfdx-git-delta

image

Generate package.xml for changed components only

When we talk about the delta deployment that means we need to generate the package.xml at run time and the package.xml will contain only the component that has been changed by the developer.

Add the below step after the authentication with Salesforce

- name: Generate the package.xml for delta files
  run: |
    mkdir delta
    sfdx sgd:source:delta --to "HEAD" --from "HEAD~1" --output "./delta" --ignore-whitespace -d -i .sgdignore
    echo "--- package.xml generated with added and modified metadata ---"
    cat delta/package/package.xml

Deploy delta components to Salesforce

After you have generated the package.xml with the changed components only. Add the step to deploy the delta components to the salesforce

- name: Deploy Delta components to Salesofrce
  run: |
    sfdx force:source:deploy -x delta/package/package.xml -c -l RunLocalTests -u HubOrg

Commit & publish the YML file.

Note- Delete the other deployment step

Test the delta deployment

Because we are done with the changes that we need in the pipeline .yml file. Let’s make some changes to the code base while we are on the developer branch. Create a pull request and merge the changes.

Click on the build Job to see the outcome of your Job. You will see the outcome like below image

The deployment is failing due to some code coverage. If everything is ok your pipeline will be a success image

Final Code for Production Pipeline

name: Production Pipeline
run-name: ${{ github.actor }} is running pipeline on ${{ github.repository }}
on: 
  pull_request:
    types: [closed]
    branches:
      - master
      - main
    paths:
      - 'force-app/**'
      
jobs:
  Explore-GitHub-Actions:
    runs-on: ubuntu-latest
    steps:
      - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
      - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
      - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
      - name: Check out repository code
        uses: actions/checkout@v3

      - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
      - run: echo "🖥️ The workflow is now ready to test your code on the runner."
      - name: List files in the repository
        run: |
          ls ${{ github.workspace }}
      - run: echo "🍏 This job's status is ${{ job.status }}."
      
  build:
    runs-on: ubuntu-latest
    environment: production
    steps:
      # Checkout the Source code from the latest commit
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
          
      - name: Install NPM
        run: |
          npm install
      # Install the SFDX CLI using npm command
      - name: Install the SFDX CLI
        run: |
          npm install sfdx-cli --global
          sfdx force --help
          
      - name: Install the sfdx-git-delta plugin
        run: |
          echo 'y' | sfdx plugins:install sfdx-git-delta
          
      - name: Decrypt the server.key.enc file & store inside assets folder
        run: |
          openssl enc -nosalt -aes-256-cbc -d -in ${{ secrets.ENCRYPTION_KEY_FILE }} -out ${{ secrets.JWT_KEY_FILE }} -base64 -K ${{ secrets.DECRYPTION_KEY }} -iv ${{ secrets.DECRYPTION_IV }}
          
      - name: Authenticate Salesforce ORG
        run: |
          sfdx force:auth:jwt:grant --clientid ${{ secrets.HUB_CONSUMER_KEY }} --jwtkeyfile  ${{ secrets.JWT_KEY_FILE }} --username ${{ secrets.HUB_USER_NAME }} --setdefaultdevhubusername -a HubOrg --instanceurl ${{ secrets.HUB_LOGIN_URL }} 
      
      - name: Generate the package.xml for delta files
        run: |
          mkdir delta
          sfdx sgd:source:delta --to "HEAD" --from "HEAD~1" --output "./delta" --ignore-whitespace -d -i .sgdignore
          echo "--- package.xml generated with added and modified metadata ---"
          cat delta/package/package.xml
      
      - name: Deploy Delta components to Salesofrce
        run: |
          sfdx force:source:deploy -x delta/package/package.xml -c -l RunLocalTests -u HubOrg

Integrate the Static Code Analysis Tool

It is very important that we keep our code clean that follow all the best practices to get rid of technical debt in your code, making sure the code is not vulnerable, and other security issues are being taken care of at the early stage of the development

Install the SFDX CLI Scanner

Because Salesforce has its own plugin to perform the static code analysis. We will be using SFDX CLI Scanner plugin to analyze the code vulnerable.

Add the step to install the scanner in your pipeline before the deployment step

- name: Install the SFDX CLI Scanner
  run: |
    echo 'y' | sfdx plugins:install @salesforce/sfdx-scanner

Run the Code Analysis tool in the repo

The above step will install the scanner and now, we need to run the Scanner to scan all our code and generate the report. Add a new Step to scan the code

- name: Run SFDX CLI Scanner
  run: |
    sfdx scanner:run -f html -t "force-app" -e "eslint,retire-js,pmd,cpd" -c "Design,Best Practices,Code Style,Performance,Security" --outfile ./reports/scan-reports.html

Upload the Scan report as Artifacts

It is very important to store the scan result as artifacts so that developers can download and refer to the reports to make the changes to the code that may cause the technical debt

- uses: actions/upload-artifact@v3
  with:
    name: cli-scan-report
    path: reports/scan-reports.html

image

Work with dependent Jobs

Sometimes you have a requirement that one job needs to wait until the other job has been completed. For Example, the job build need to wait Explore-GitHub-Actions job to be executed then only build the job will execute.

Use needs tag in the dependent job and add all the controlling job commas separated within [].

build:
  runs-on: ubuntu-latest
  needs: [Explore-GitHub-Actions]
  environment: developer

image

Make changes in the codebase, commit and publish the changes to execute the job.

imageThanks for reading. Feedbacks are welcome

Resources

Amit Singh
Amit Singhhttps://www.pantherschools.com/
Amit Singh aka @sfdcpanther/pantherschools, a Salesforce Technical Architect, Consultant with over 8+ years of experience in Salesforce technology. 21x Certified. Blogger, Speaker, and Instructor. DevSecOps Champion
Share this

Leave a review

Excellent

SUBSCRIBE-US

Book a 1:1 Call

Must-read

How to start your AI Journey?

Table of Contents Introduction Are you tired of the same old world? Do you dream of painting landscapes with your code, composing symphonies with data, or...

The Secret Weapon: Prompt Engineering: 🪄

Table of Contents Introduction Crafting the perfect prompt is like whispering secret instructions to your AI muse. But there's no one-size-fits-all approach! Exploring different types of...

How to Singup for your own Production org?

Let's see how we can have our salesforce enterprise Salesforce Org for a 30-day Trial and use it as a Production environment. With the...

Recent articles

More like this

LEAVE A REPLY

Please enter your comment!
Please enter your name here

5/5

Stuck in coding limbo?

Our courses unlock your tech potential